【Django】模型层说明

【Django模型层】

  之前大概介绍Django的文章居然写了两篇。。这篇是重点关注了Django的模型层来进行学习。

■  模型定义

  众所周知,Django中的模型定义就是定义一个类,其基本结构是这样的:

from django.db import models

class ModelName(models.Model):
    field1 = models.XXField(...)
    field2 = models.XXField(...)

    class Meta:
        db_table = ...
        other_metas = ...

 

  模型类的子类Meta决定了一些模型的元数据,比如数据库中对应的表名,表的默认排序方式等等

  Meta类的属性已经有Django进行了预定义,一般不需要特地再去改变。Meta类有以下这些属性可供我们改变

  abstract  True或者False,标识本类是否是抽象基类。一般不动

  app_label  本模型所属模块的名称,一般不动

  db_table  改变此值可以改变对应这个模型的数据库中的表,如果不指定的话那么在Django会按照默认规则取一个表名,规则是全小写的appname_modelname

  get_latest_by  通常指向本模型中的一个时间或者日期字段,以获得开始和结束截取记录的标志

  managed  True或者False,指出是否可以通过manage.py管理这个模型

  ordering  本模型的默认排序依据字段,默认是以降序排序

  default_permissions  模型操作权限,默认为default_permissions = ('add','change','delete')

  其他还有一些奇奇怪怪的属性可以设置,这里不说了

 

  ●  普通字段选择

  XXField就是指普通的字段了,普通字段的选择主要有以下这些:
  AutoField  递增的整型字段,相当于一个自动递增的id,如果没有设置Django也会自动帮我们的模型添加(尤其是没有指定主键的时候)

  BooleanField  布尔值字段,当被设置为表单类的Meta子类的model时,这个表单类中对应的字段在HTML上的表现是<input type="checkbox">

  CharField  比较短的字符串,相当于<input type="text">

  TextField  长文本字段,相当于<textarea>

  CommaSeperatedItegerField  规定输入只能是用逗号分隔开的数字的<input type="text">,这个Field已经过时,以后不支持了

  DateField/DateTimeField  规定输入只能是日期/时间日期的input,格式是%Y-%m-%d %H:%M:%S。在admin界面上,这个字段非常好用因为有比较友好的日历插件使用。不过在自己渲染的界面中,还是一个普通的input,需要进一步改善

  DurationField  时间段的字段,用Python的timedelta类构建

  EmailField  规定输入只能是Email格式的字段

  FileField  文件上传字段,在定义这个字段时必须有参数upload_to用于指出上传过来的文件在服务器端的保存路径。具体文件怎么上传可能需要单开一篇来说明

  IntegerField/FloatField  分别保存一个整数和浮点数的字段

  GenericIPAddressField  保存IP地址的字段

  NullBooleanField  和BooleanField类似,不过除了T和F外还有一个Null选项。在HTML上就是除了Yes和No以外还有一个Unknown选项

  ImageField  和FileField类似,不过会校验上传的是否是一个有效的图片文件

  URLField  校验是否是有效URL的字段

 

  ●  普通字段常用参数

  字段在初始化的时候可以加入参数以指定其更多的性质,常用的参数有以下这些:

  default  设置字段的默认值

  help_text  设置HTML中输入控件的帮助字符串,从HTML代码来看就是在input标签后面加上了个span.help_text标签

  null  是否允许字段是没有值,默认为False

  blank  是否允许字段是空值,和null的区别在于,null设置的是数据库模型表结构中字段是否能是空,而blank则是规定了HTML界面上的input能否提交空值

  choices  一个二元元组的列表,每个元组的第一项是实际值,第二项是HTML界面上显示的文字

  primary_key  是否为主键

  unique  值是否要唯一,定义数据库的唯一约束

  除了上面这些参数之外,还可以在所有参数前面加上一个无指定名字的参数用来表示HTML界面上输入框前面的文字。一般如果不设置这个参数的话那么会以这个字段对象的变量名加上首字母大写给出。

 

■  ORM操作

  我们定义了如下这么一个模型以供后续操作的演示:

from __future__ import unicode_literals
from django.db import models

class Comment(models.Model):
    headline = models.CharField(max_length=255)
    body_text = models.TextField()
    pub_date = models.DateField()
    n_visits = models.IntegerField()

    def __unicode__(self):
        return self.headline

 

  ●  基本查询

  模型类如Comment会自带一个objects对象可以通过它来实现对数据的查询:

  Comment.objects.all()  这个方法返回的就是数据库中对应表中所记录的对象。也就是类似于一个Comment类对象组成的列表,每个对象都可以调用属性headline,body_text,pub_date等等

  除了all这种傻大黑粗的方法,还有filter和exclude可以用来进行进一步的结果筛选。这两个方法的一般形式是filter(**kwargs)和exclude(**kwargs)。用法如:

  Comment.objects.filter(pub_date = '2017-11-01')  返回所有pub_date字段是2017-11-01的记录的列表

  Comment.objects.exclude(pub_date = '2017-11-01')  返回所有pub_date字段非2017-11-01的记录的列表

  很明显,filter和exclude可以像jQuery方法一样一连串地写下去。

  这么看来查询的功能似乎不是很强大,但是Django还隐藏了特别一些手段。可以先看一下下面这个语句:

  Comment.objects.filter(pub_date__day=3)  返回pub_date字段(这是一个日期字段)日期为3的记录

  耐人寻味的是pub_date__day这个东西,这是Django中独特的被称为字段查询的方式。其表现形式是“字段名称__谓词”。谓词还有很多比如:

  exact  精确等于,id__exact=14 相当于 select * from xxx where id=14

  iexact  大小写不敏感等于,head_line__iexact='SOMESTRING' 相当于 select * from xxx where upper(head_line)='SOMESTRING'

  contains  模糊匹配,相当于 字段='%keyword%'的查询条件

  in  包含,比如objects.filter(id__in=[1,2,3])

  gt  大于,n_visits__gt=3相当于n_visits > 3的条件

  gte/lt/lte  和gt类似,分别是大于等于,小于,小于等于

  startswith/endswith  相当于'keyword%'和‘%keyword’这样的where子句条件

  range  比如start_date = datetime.date(2017,1,1)  end_date=datetime.date(2017,12,31),然后filter(pub_date__range=(start_date,end_date))来表示筛选出pub_date在2017年内的一些数据。从这个例子中也可以看出,Django的DateField其实是基于python中的datetime.date类来的。

  year/month/day/week_day  针对日期时间的字段可以进行进一步的筛选

  isnull  是否为空,比如pub_date__isnull=True就是筛选出了所有pub_date字段为NULL的所有记录

  更详细的查询可以参考【https://www.cnblogs.com/ajianbeyourself/p/3604332.html】

  

  还有就是和flask的ORM类似的,有个get方法可以根据id来获取单条记录。SQL中的limit,offset等关键则可以通过对all()返回的列表进行切片操作来实现:

  Comment.objects.all()[:10]

 

  上面的查询都还是单表查询,如果涉及到连接查询呢?连接查询的建立方法在下面讲,这里先借用下面三个例子中的一对多关系的实例。如果想通过多表条件查询一表信息的时候可以用到distinct()方法,相当于是从多表条件筛选出来的一部分结果集中进行set,再通过外键就可以得到一表中的结果集。改写称为ORM的方法序列的话大概是这样: Account.objects.filter(contact__多表属性名__条件名),这里的contact是表名Contact的小写,从一表出发,查询多表的某个属性符合某个条件的数据记录。类似的通过一表条件查询多表记录时:Contact.objects.filter(account__一表属性名__条件名),这里的account不一定是表名小写,而是定义Contact表时定义成外键的那个字段的名字。

 

  ●  花样查询

  查询操作是非常灵活的,比如我们可以动态地指定条件字段,可以用AND,OR等多种逻辑串联条件,可以指定输出的字段等等。下面针对更加灵活一点的查询做出解决方案的说明。

  动态的指定条件可以改变传递参数的形式实现。比如filter,get方法接收的其实是**kwargs,因此我们可以通过filter(**{'key1','value1', 'key2': 'value2'})的形式来进行动态条件字段的结果集过滤或单个对象的获取。

  指定输出字段通常通过values方法指定,比如values('field1','field2')使得输出只有这两个字段的值而不是全部。values方法返回的是比较标准的JSON格式数据,而另一个方法values_list可以返回tuple的list这样一种形式的数据

  排序可以通过order_by()方法完成,默认升序,如果要降序可以在后面再加上reverse()

  对于简单的条件进行AND逻辑连接,可以直接在后面写,但是如果还要进行OR连接,或者条件很多很多写起来很麻烦,那么可以通过Q对象来进行条件的逻辑处理。Q对象的基本用法如下:

from django.db.models import Q

# 将常用的长条件固化下来:
q = Q(name__startswith='F')

# 逻辑串联多个条件:
MyModel.objects.filter(
  Q(name__startswith='F') | Q(ip__contains='127')  # OR条件
)

'''
用&连接两个Q对象自然就是AND条件
在Q对象前面加上~如~Q(xxx)就是代表非,即NOT条件
若在filter,get等可以填充Q对象的地方传入了多个Q对象作为参数,那么默认这些Q对象以AND逻辑连接如get(Q(xxx),Q(yyy))等同于get(Q(xxx) & Q(yyy))
'''

  实际开发过程中还偶尔会遇到需要比较同一个记录中的两个不同字段,或者在写操作链的代码的时候就需要进行对记录某个字段的值进行引用的时候,就需要用到F对象:

from django.db.models import F,Q

# 同时用了Q和F,这个filter过滤出来的是所有public_ip字段不等于private_ip字段值的记录
SLB.objects.filter(~Q(public_ip=F('private_ip')))

# F对象之间还可以做简单的数学运算,这里就是将所有记录按照amt字段与price字段的值的乘积进行排序的前十位
totalPriceList = Good.objects.all().order_by(F('amt')*F('price')).reverse()[:10]

# 通过F对象也可以跨表调取字段值,只要表达合法即可
Module.objects.filter(port__in=F('for_server__behind_slb__intern_ports'))

 

 ● 查询结果的简单统计处理

  在SQL中常见的统计处理就是count, max, avg之类的。另外统计还常常和group by一起出现。针对以上这些场景分别给出django orm的解决方案。

  首先要介绍的是四个最常用的统计方法,在django.db.models中的Count,Avg,Max,Sum。

  针对Count最简单的统计有多少条结果可以直接通过结果集调用count方法。Avg和Max两个方法通常和aggregate结合起来用,如下所示:

# 假设我们有一个叫Server的模型,其中有mem_info字段,是bigint,记录服务器内存量。Server对应的表名叫server,将与orm操作等同的sql写在后面

s = Server.objects.all()
s.count()  # 1.select count(*) from server;
s.aggregate(Avg('mem_info'))  # 2.select avg(mem_info) from server;
s.aggregate(mem_avg=Avg('mem_info'))  # 3.select avg(mem_info) as mem_avg from server;

s.aggregate(max_mem=Max('mem_info'))  # 4.select max(mem_info) as max_mem from server;

s.aggregate(sum_mem=Sum('mem_info')) # 5. select sum(mem_info) as sum_mem from server;

  上述aggregate返回的不是一个QuerySet,而是一个字典。比如3返回的{"mem_avg": xxxxx},2返回的是{"mem_info__average": xxxx}(默认字段名是这样的)。如果想要同时获取多个统计值,那么直接在aggregate中写多个参数即可,如aggregate(max_mem=Max('mem_info'), mem_avg=Avg('mem_info'))

  接下来就是介绍如何通过orm实现简单的groupby了。主要用到的是取指定列值的values和组分聚合方法annotate。示例如下:

# 假设Server模型还有字段environment,下面根据环境(生产、测试等)组分分别统计

s.values('environment').annotate(server_count=Count('environment'))  # 1. select environment,count(environment) as server_count from server group by environment;

s.values('environment').annotate(max_mem=Max('mem_info'))  # 2.select environment,max(mem_info) as max_mem from server group by environment;

  values后面直接接上annotate的话,就有了group by的功能。annotate之后返回的也不是简单的字典,而是一个QuerySet对象。但是和objects.all()等得到的QuerySet不同的是,它更像一个字典的生成器。比方说1的返回可能是 <QuerySet [{'environment': u'0', 'server_count': 2}, {'environment': u'1', 'server_count': 1}]>,表明environment值为0的Server有两个,而值为1的有一个。

  同理,2的返回可能是<QuerySet [{'environment': u'0', 'max_mem': 4194304}, {'environment': u'1', 'max_mem': 2097152}]>,这代表相应environment值的组分中mem_info字段最大分别是多少。针对这两个QuerySet我们可以for server in xxx去遍历取值。

 

  再来细说下annotate这个方法。在上面的实践中,我们已经可以区分到,QuerySet对象其实分成两种。一种是通过类似于Server.objects.all()得到的一个可迭代,并且其中每一个元素都是一个ORM对象;另一种则是通过Server.objects.all()[0].values()获取到的数据结构。后一种本质上每个元素是一个字典。当然在values方法中指定一些字段名的话还可以精简化这个字典的结构。

  annotate最常见的调用方法,是接在后一种形式的QuerySet后面。如Model.objects.values('colA','colB').annotate(alias=Count('colA'))这个等价成SQL的话就是:

  SELECT colA, colB, count(colA) as alias FROM table GROUP BY colA, colB;

  其他annotate也还有很多很魔幻的用法… 一旦稍微复杂一点,annotate就要爆炸了… 连官方文档都指出,情景较为复杂的时候,设计annotate相关逻辑时最好的准则就是,这个语句能够取到正确的数据那么他就是正确的,不要太深究其原理了。 所以在有些时候,ORM可能还是直接写SQL更好。

 

  ●  数据保存和删除

  Django的ORM没有区分insert和update,只有统一的save方法。比如下面这样的:

new_comment = Comment(headline='Headline DDD',pub_date=datetime.datetime.now(),n_visits=0)
new_comment.save()
###此时库中已经有了一条新纪录,是insert进去的###

id = new_comment.id
comment = Comment.objects.get(id__exact=id)
###这步其实是多此一举的,不过为了模拟下入库后再从库中取数据的过程###

comment.body_text = 'Body Text DDD is Here.'
comment.save()
###此时只是改变了对象的某个字段的值,save其实就是update语句###

 

  

  删除记录很清楚就是用delete方法了:

  Comment.objects.get(id__exact=13).delete()

  

  还有一个常用的操作是复制。djangoORM没有直接给出复制的操作接口,不过我们可以通过将一个对象赋值给一个变量,然后改变此变量的主键值再save这个变量,同样可以达到复制的目的。如果在一些模型中主键是自增的AutoField的话可以置主键值为None,再save,django就会自动帮我们设置一个新的合理的自增主键值。如:

new_server = Server.objects.get(id=100)
new_server.id = None
new_server.save()

   这样的复制方法无法处理一些复杂的关系,比如一对一关系时要考虑主键冲突而引起做一些额外的处理;多对多关系也不会被自动复制,因此需要手动额外的进行类似new.relations.set(old.relations.all())这样的操作(另外注意,如果按是old = xxx.objects.get(xxx),然后new = old的话,改变new的值old也会被改变,应该使用copy模块的deepcopy(old)的方式来赋值);对于一对多关系则有两种情况,复制操作的模型在关系中是“多”侧,则会被复制过去,如果是“一”侧,那么也要进行手动的信息复制的维护。

 

  ●  关系操作  

  我有种非常蛋疼的预感。。

  和flask中的ORM一样,关系分成一对一,一对多,多对多关系。

  就一对一关系而言,就是制定两张表的主键一样,那么在建模时可以用models.OneToOneField来实现:

class Accout(models.Model):
    user_name = models.CharField(max_length=80)
    password = models.CharField(max_length=255)
    reg_date = models.DateField()
    def __unicode__(self):
        return 'Account: %s' % self.user_name

class Contact(models.Model):
    account = models.OneToOneField(
        Account,
        on_delete=models.CASCADE,
        primary_key=True
    )
    zip_code = models.CharField(max_length=10)
    address = models.CharField(max_length=80)
    mobile = models.CharField(max_length=20)

    def __unicode__(self):
        return '%s %s' % (self.account.user_name,mobile)

   首先看到OneToOneField并不是像CharField这种一样,传的参数就是要做关联的那个类,on_delete参数指定了当关联模型记录被删除时本模型的相关记录如何处理的方式,CASCADE是将本模型的相关记录一并删除。

  在这么处理之后我们先python manage.py makemigrations mytest和python manage.py migrate mytest来同步数据库,完成之后可以python manage.py shell来进入shell模式,这样可以更清晰地看模型间的关系:

>>>from mytest.models import Account,Contact
>>>a1 = Account(user_name='david',password='0',reg_date=datetime.date.today())
>>>a2 = Account(user_name='rose',password='0',reg_date=datetime.date.today())
>>>c1 = Contact(account=a1,mobile='13100010001')
#注意这里,其实是把a1作为对象传递给account参数,以表明c1这条记录和a1这条记录之间的联系
>>>c1.save()
>>>print c1
<Contact: david,13100010001>
>>>print c1.account
<Account: david>
>>>print a1.contact
<Contact: david 13100010001>

  至于为何彼此的属性叫account和contact而不是Account,Contact,我只能认为是Django的设定就是这样了。。

 

  一对多关系模型,比如1对N的模型建立,主要是靠在N表中建立一个外键列,这个列的取值只能是1表中的主键。对应到Django的models里面就是使用models.ForeignKey,如:

from django.db import models

class Account(models.Model):
    user_name = models.CharField(max_length=80)
    password = models.CharField(max_length=200)
    reg_date = models.DateField()

    def __unicode__(self):
        return 'Account: %s' % self.user_name

class Contact(models.Model):
    account = models.ForeignKey(Account,on_delete=models.CASCADE)
    zip_code = models.CharField(max_length=10)
    address = models.CharField(max_length=80)
    mobile = models.CharField(max_length=20)

    def __unicode__(self):
        return '%s: %s' % (self.account.user_name,self.mobile)

 

  在建立这样的模型之后我们就可以通过shell进行如下实验:

>>>a1 = Account(user_name='Rose',password='0',reg_date=datetime.date.today())
>>>a1.save()
>>>c1=Contact(account=a1,mobile='13100010001')
>>>c1.save()
>>>c2=Contact(account=a1,mobile='13100010002')
>>>c2.save()
>>>print c1.account == c2.account
True
>>>print a1.contact_set
[<Contact: Rose,13100010001>,<Contact: Rose,13100010002>]
>>>print a1.count_set.count()
2
>>>a1.delete()
#由于on_delete设置的是CASCADE,所以当a1被删掉之后,关联在a1上的c1和c2也都会被删除

  多对多关系模型的建立一般而言需要一张第三表来记录这种关系,在Django中用models.ManyToManyField来表示。比如继续沿用上面的Account和Contact两个模型(把Contact中的account字段名改成accounts以体现多对多关系,另外需要注意的是,多对多关系是没有on_delete选项的。),把关联的方法变成ManyToManyField之后,进行如下shell操作:

>>>a1=Account(user_name='Leon')
>>>a1.save()
>>>c1=Contact(mobile='13100010001')
>>>c1.save()

>>>c1.accounts.add(a1)
#通过Contact对象建立联系,将a1和c1关联了起来

>>>a2=Account(user_name='Terry')
>>>a2.save()    #一定要save过,否则建立联系会报错
>>>c2=Contact(mobile='13100010002')
>>>a1.contact_set.add(c2)    #Leon的第二个联系方式
>>>a1.save()
>>>a1.contact_set.remove(c1)    #消除单个对象的关联
>>>a1.save()
>>>a1.contact_set.clear()    #清除所有对象的关联

   ***一点对多对多关系的说明:

  上面的演示是基于那本红书的,而里面的django版本可能比较老,在我实验中,已经不需要加_set,直接引用属性名加上all,add,remove等方法即可(那是因为这是关系字段的发起者。。比如在模型A中定义字段b=ManyToManyField(B),然后A模型的实例a可以a.b.all(),而反过来引用时就要用到_set,如b.a_set.all()这样子)。

  另外当一个表连续关联了两次另一个表时,为了通过那个表对象引用这个表信息时不出现混淆,需要在设置关系的方法(比如OneToOneField或者ManyToManyField等)中加上related_name参数,来为不同字段引用设置不同的关联名称。

  可以看到,无论是定义还是操作中,我们都没有直接涉及到传说中的第三张表,这也就是说Django帮我们封装了这一过程,使得框架的使用者不用再维护第三张关系表了。

 

  ●  其他操作

  Module.objects.values('field_name'[, 'other_field',...])该模型对应表中指定若干列的所有值,返回是一个字典的列表。

 

■  ORM的实际SQL显示

  ORM的原理,实际上是将程序中的代码逻辑转化成SQL,然后通过数据库连接去执行这些SQL并返回结果。所以如果ORM执行效率比较低或者怎么样的时候,可以通过调取ORM操作对应的原生SQL来查看优化。

  对于查询操作,由于ORM的查询操作载体都是QuerySet类的对象,而这类对象的有一个属性叫做query。这个query属性实现了__str__方法,打印出来就是这个QuerySet对应的SQL了。

  对于其他操作,略微麻烦一点。有一个办法就是from django.db import connection,然后再调取connection.queries这个属性。这个属性是一个列表,其中按照时间顺序从前到后记录了一个数据库连接执行过的所有SQL,其中也包括除了查询操作之外其他一些类型的操作。

■  ORM继承

  Django模型层的ORM另一个比较强大的地方在于可以进行模型的继承。这也正是有机结合了面向对象编程中的类继承的思想。根据不同的继承手段,继承大概可以被分成三种:

  ●    抽象类继承

  就像java中可以继承抽象类一样,这里的抽象类就是一个不在数据库中落地,但是可以被其他表类继承其中字段的基类。在多个表中有若干相同的字段的时候,我们就可以搞一个基类,避免重复定义这些字段:

class MessageBase(models.Model):
    id = models.AutoField()
    content = models.CharField(max_length=120)
    user_name = models.CharField(max_length=80)
    pub_date = models.DateField()

    class Meta:
        abstract = True

class Moment(MessageBase):
    headline = models.CharField(max_length=50)

LEVELS = (
  ('1','Very Good'),
  ('2','Good'),
  ('3','Normal'),
  ('4','Bad')
)

class Comment(MessageBase):
    level = models.CharField(max_length=1,choices=LEVELS)

  在子类模型的实例中,可以直接用普通的方法来引用在父类模型中定义的字段。

  ●  多表继承

  所谓多表继承和抽象类继承基本一样,就是不设置abstract=True,然后父类的这张表也是在数据库中落地的,仅此而已。。可以说是比较正常的,符合预期的表继承方法

  ●  代理模型继承

  上面两种方法中,继承父类的子类最终都是会实际储存数据到数据库中的,而代理模型继承中,继承下来的子类是不存储数据的,比如:

from django.db import models

class Moment(models.Model):
    id = models.AutoField()
    headline = models.CharField(max_length=50)
    content = models.TextField()
    user_name = models.CharField(max_length=20)
    pub_date = models.DateField()

class OrderedMoment(Moment):
    class Meta:
        proxy = True  #这个就是代理模型继承的方法
        ordering = ['-pub_date']

  上面的OrderedMoment模型,只是借用了父模型Moment中的数据并且进行了一个排序,自身不实际存储数据。

 

■  migrate和migrations

  用了一段时间django之后,发现manage.py migrate以及makemigrations等操作真的很迷惑。。至今还没弄得很明白。

  需要知道的是,这个migrations的记录会以数据库结构的变化(实际的变化),模块中migrations目录下的文件的生成(变化的内容记录)以及数据库内django_migrations表中记录的新增(变化的总体记录),这样三种方式体现出来。三个地方只要出现不统一的情况,就可能会出现各种各样的问题。。

  总体来说,简单的实践就是在models中定义模型,然后makemigrations生成migrations的文件,然后migrate,将本次model的变更反映到数据库结构中去,同时也会再django_migrations表中记录下变更。模型的变更频率应该要适当。如果太短则会导致每次生成一个migration文件,最终导致migrations目录下有很多很多文件。如果太长则一次性变化太多,可能会发生很多意想不到的错误,更折磨人。

  相比之下,还是相对快地更新迭代比较保险,因为有方法可以整合掉多余的migrations文件并且保证能继续按照上述实践迭代模型。下面来介绍两种方法,请注意这些方法的难点在于整合掉一些migrations文件后整个模型层的正常工作如正常的模型变更迭代等是否还能顺利进行:

  * 友情提示: 操作前请备份数据以防万一。

  第一种,如果对于数据库中现有内容不要求保留,那就很简单。首先把数据库中所有表清空(甚至直接drop掉整个数据库再重新建一个),然后将所有模块下的所有migrations文件删除(除了每个migrations目录中的__init__.py文件,这是包提示文件,不能删)。然后manage.py makemigrations,manage.py migrate,就可以将定义与models.py中的那些模型映射成数据库中的表了。很显然这种方法虽然能够保存下数据库的表结构,但是只能用用在开发环境中,而且就算是开发环境有时候会有比较多的数据,直接这么暴力全部删除也不好。

  第二种方法是django给出的一种解决方案。其操作流程是这样的:

  首先要确认表结构已经和migrations以及django_migrations中的记录对应了。确认方法可以是manage.py makemigrations来看下是否提示no changes(当然也有少数情况每次make的时候都会有更新,比如某个DateField被指定了default=datetime.datetime.now()之类的,这种就可以忽略了。)。另外manage.py showmigrations命令可以查看migrate和现有migrations文件的对应关系。文件前面的框框里有叉表示这个文件已经被migrate了。

  然后运行命令 ./manage.py migrate --fake app_name zero,app_name是你要重置整合migration文件的模块名。这一步过后,该模块的migrate记录会被重置,可以再showmigrations一下看到,这个模块的所有文件前面都没有叉了。接下来就可以删掉除了__init__.py之外migrations下所有文件了。当然为了以防万一,强烈推荐同目录下创建一个backup目录,把这些文件暂时先放进去,不要真的删掉。

  然后再执行 ./manage.py makemigrations。此时可以看到在migrations下面有了一个0001_initial.py文件,这个文件中有完整的表结构。

  最后一步,执行 ./manage.py migrate --fake-initial ,这一步将数据库的django_migrations表中,本模块的所有记录删除并记录成我们只migrate到了0001_initial,(所以叫fake)。不能真执行的原因是因为目前数据库中是有结构的,而尝试真的执行会去建表干嘛的,当然就会报错了。

  至此,之前漫长的migrations文件都没有了,取而代之的是从头开始的0001。 

  *这样做也不是万事大吉了。。比如碰到开发、测试、生产环境中,只有一个做了migration文件压缩操作,另外的没有,此时如果再来一个新的migration文件,会因为编号在各个环境中不匹配(在压缩过的那个环境中编号肯定是0002)导致无法通用。

 

  上面这种方法尽量不要绕过django完成,比如不能手动删掉所有migrations文件,再删django_migrations记录,然后再重新makemigrations,会出错。。因为此时数据库结构没有回滚。

 

  ●  关于fake和fake-initial参数 以及其他的一些migrate可选用参数

  migrate命令的--fake参数上面也用到过了。这个参数在官方文档中的解释是,记录或消除migrate记录,但不去真的运行SQL以改变数据结构。换句话说,一般的migrate的流程是1. 读取migrations文件,解析成SQL;2. 执行SQL改变数据库结构或内容;3. 将本次migrate的信息录入django_migrations。但是如果加上--fake参数,那么第2步会被直接跳过而变更直接被记录到django_migrations中。

  这个参数的使用场景常常是这样的: 当一个migration文件执行出错,而我们明确知道这个错该如何修正,我们完全可以手动去对数据库结构做一些修改。然后再带--fake参数地运行本次变更对应的migration文件。“假装”我们正确地做了一次migrate。

  --fake-initial的原理是类似的,只不过其针对的只是执行0001_initial这个文件。所以说fake-initial的使用场景更加狭窄,即上面所说的对于数据库中有结构,但是0001_initial.py重新生成并且需要写入django_migrations记录时,用到这个参数。

 

  另外migrate还有一个比较有用的参数是sqlmigrate。其用法是migrate sqlmigrate app 000x_xxx。它的意思是将某个特定的migrations文件翻译成SQL打印到屏幕上。我们手动去执行这些SQL效果和自动去migrate的效果是一样的。因此在自动migrate时出错的时候,可以利用sqlmigrate打印出SQL,具体查看哪些SQL有问题。如果可以排除,那么可以手动修改出错的SQL并执行,再带有--fake参数执行下这个migrate即可。

posted @ 2017-11-10 15:59  K.Takanashi  阅读(1000)  评论(0编辑  收藏  举报