首页 > 代码库 > django学习之Model(二)

django学习之Model(二)

继续(一)的内容:

1-跨文件的Models

在文件头部import进来,然后用ForeignKey关联上:

from django.db import modelsfrom geography.models import ZipCodeclass Restaurant(models.Model):    # ...    zip_code = models.ForeignKey(ZipCode)

2-field名字的约束

1)-不能是Python预留字

2)-不能有连续的2个下划线,例如foo__bar,2个连续下划线是django的query查询的语法。

field的名称不必匹配database中的column的名称。SQL的保留字也可以作为model中field的名称,因为django躲开了SQLquery查询中的所有的database的table名称和column名称。django使用database engine的引用语法。

3-自定义的一些field类型

如果常用的model field不能满足需要,可以自定义一些field。完备的资料参考Writing custom model fields.

4-Meta选项

可以给model加一些内容,用内置的class Meta来实现:

from django.db import modelsclass Ox(models.Model):    horn_length = models.IntegerField()    class Meta:        ordering = ["horn_length"]        verbose_name_plural = "oxen"

Meta里放的东西可以是任何非field的东西(应该是一些数据,方法在def的函数中定义,下边将会提到),例如 verbose_name 也可以放进来。Meta不是必须的,可以参考 model option reference.

5-Model中的一些方法

Manager的方法能处理一些广义上的普适的事情,更具体的事情可以在model中自己定义方法来处理:

from django.db import modelsclass Person(models.Model):    first_name = models.CharField(max_length=50)    last_name = models.CharField(max_length=50)    birth_date = models.DateField()    def baby_boomer_status(self):        "Returns the person‘s baby-boomer status."        import datetime        if self.birth_date < datetime.date(1945, 8, 1):            return "Pre-boomer"        elif self.birth_date < datetime.date(1965, 1, 1):            return "Baby boomer"        else:            return "Post-boomer"    def _get_full_name(self):        "Returns the person‘s full name."        return %s %s % (self.first_name, self.last_name)    full_name = property(_get_full_name)

 最后一个方法是一个property.

model instance reference 中会有完整的自动产生的方法,下边给出一些常用到的,需要重写的 方法:

__str__()

python 3中与__unicode__()相同

__unicode__()

Python神奇的方法,可以把任何对象转换成unicode编码的字符串。值得注意的是,这个方法通常会用在想要把一个对象呈现在可交互的控制面板(console)中。通常都会自定义重写来用,默认的一般没啥用。

get_absolute_url()

这个是告诉django如何来计算URL的,任何一个有唯一URL的对象,都应该定义这个方法。

6-重写预定义的model的方法

还有一类封装了database中的方法的model方法,这些model中的方法也可以自定义。尤其是当你经常想改变save()或delete()。例如,想在save()的时候做点其他的事情,就可以自定义save(),在里面添加相应的方法,如下:

from django.db import modelsclass Blog(models.Model):    name = models.CharField(max_length=100)    tagline = models.TextField()    def save(self, *args, **kwargs):        do_something()        super(Blog, self).save(*args, **kwargs) # Call the "real" save() method.        do_something_else()

 

 也可以阻止save发生,灵活度很高:

from django.db import modelsclass Blog(models.Model):    name = models.CharField(max_length=100)    tagline = models.TextField()    def save(self, *args, **kwargs):        if self.name == "Yoko Ono‘s blog":            return # Yoko shall never have her own blog!        else:            super(Blog, self).save(*args, **kwargs) # Call the "real" save() method.

 

特别要注意,记得在重写方法中一定要调用superclass方法,上例中的

 super(Blog, self).save(*args, **kwargs) # Call the "real" save() method.

 

否则,默认的(被你的重写覆盖掉的)方法是不会执行的,这样数据就不能被保存。同样重要的还有,传递的变量*args, **kwargs.  django总是会用添加新的变量的方式来拓展预置的方法的能力。如果在自定义的方法中用到了*args和**ksargs,要保证所写的代码能够自动支持这些变量,当他们被添加的时候。(*args和**kwargs参考这篇博客)

提示:重写的model中的方法在批量操作时不会被调用。delete()在用QuerySet进行批量操作时就没必要,为了保证自定义的delete方法被执行,可以用pre_delete或post_delelte。不幸的是,creating或updating在批量操作时都不能用,同理save(), pre_save, post_save.注意这只是说的被重写了的方法。

7-操作自定义的SQL

另一种常见的形式是在model 的方法中写自定义的SQL声明。更多信息参考using raw SQL

8-Model的继承

跟Python的类的继承相似,别忘了在文件的头部写上:django.db.models.Model

django中有3种常见的继承:
1)-父类只用来被继承,父类并不自己生成database table。请参考Abstract base classes

把一些共同的信息放到基类(base class)中去,然后在base class 中的Meta里,令abstract=True,这样,这个base class 就不会去创建database table了,然后用子类去继承这个抽象基类:

from django.db import modelsclass CommonInfo(models.Model):    name = models.CharField(max_length=100)    age = models.PositiveIntegerField()    class Meta:        abstract = Trueclass Student(CommonInfo):    home_group = models.CharField(max_length=5)

 

注意:base class中的field名称与子类中的field名称不能重复。

同样对于Meta也可以继承:

from django.db import modelsclass CommonInfo(models.Model):    # ...    class Meta:        abstract = True        ordering = [name]class Student(CommonInfo):    # ...    class Meta(CommonInfo.Meta):        db_table = student_info

 

可能会有疑问,这样不是把基类的abstract=True也继承下来了,然后这个子类又成为了abstract了吗?不会的,django会自己处理掉这个abstract,除非你就是要再来个abstract类,然后再去创建子类的子类,这样你需要自己打开abstract=True. 当然,基类中不能出现如db_table这样的参数,因为所有它的子类都会继承这个参数,然后生成了相同名称的database table.

另外还要注意related_name.  如下:

#given an app common/models.pyfrom django.db import modelsclass Base(models.Model):    m2m = models.ManyToManyField(OtherModel, related_name="%(app_label)s_%(class)s_related")    class Meta:        abstract = Trueclass ChildA(Base):    passclass ChildB(Base):    pass

 

#given another app rare/models.pyfrom common.models import Baseclass ChildB(Base):    pass

 

如果没有用到related_name的话,则rare下的ChildB和common下的ChildB会产生相同的database table的名字。%(app_label)s会用所在app的名字替换,%(class)s会用所在的子类的名字替换。在上述例子中,common.ChildB.m2m的reverse name就是common_childb_related.rare.ChildB.m2m的reverse name就是rare_childb_related.如果没有related_name,django会在syncdb的时候报错。另外就是,related_name是在base class中使用的。如果没有用related_name,则子类中的field的revers name会是class name_set,例如,childa_set,childb_set.

2)-父类不但用来被继承,而且自己也能够生成database table,则参考Multi-table inheritance:

from django.db import modelsclass Place(models.Model):    name = models.CharField(max_length=50)    address = models.CharField(max_length=80)class Restaurant(Place):    serves_hot_dogs = models.BooleanField()    serves_pizza = models.BooleanField()

 

因为父类子类分别自己生成database table,所以下边这两个query都能够访问到数据:

>>> Place.objects.filter(name="Bob‘s Cafe")>>> Restaurant.objects.filter(name="Bob‘s Cafe")

 

如果在已经保存数据的database table中,有一个table,是从Place继承来的Restaurant,则下边的query可以执行:

>>> p = Place.objects.get(id=12)# If p is a Restaurant object, this will give the child class:>>> p.restaurant<Restaurant: ...>

 

前提是已知id=12的table中restaurant继承自place,否则,会有Restaurant.DoesNotExist的错误。

在这种继承关系中,Meta的继承是不被允许的。因为基类中已经使用了Meta中的变量,如果子类能够继承,则在子类中同时出现了2次这个变量,则产生了冲突。(我觉得这里有必要了解django的model是怎样去联系sql语句生成database table的,以及python中继承的语法和原理,这样就能清楚明白为什么子类不继承基类的Meta.)但是有些个别的Meta中的变量还是可以继承的,如ordering和get_lates_by.例如,如果不想子类按照基类的ordering方法排序的话,可以这样写:

class ChildModel(ParentModel):    # ...    class Meta:        # Remove parent‘s ordering effect        ordering = []

 

相当于重写,重新置为空。

由于这种继承是隐藏调用了OneToOneField把子类与基类联系起来,所以很容易从基类找到子类。但是如果我在子类中用ManyToManyField来联系一个别的类的话,那么需要用related_name来区分,否则reverse的时候会因为有很多相同的名称而找不到:

class Supplier(Place):    # Must specify related_name on all relations.    customers = models.ManyToManyField(Restaurant, related_name=provider)

django会自动调用OneToOneField来连接子类和基类,如果想要自己控制此连接,可以在自己写一个OneToOneField并令parent_link=True,来表明此子类连接到基类。 

3)-最后,如果只是想修改一下一个model在Python层面的一些行为,而不是去更改model的field,则参考Proxy models.

 在2)中的继承,不同的子类都要建立属于自己的database table,也就是有自己的数据库空间来放数据。现在有这么一种子类,该子类中只有操作方法,操作的对象(即数据,database table)还是基类中的数据,这种继承叫做Proxy models.  与2)中的继承方法相同,只是没有新加field,同时proxy=True即可,如下:

from django.db import modelsclass Person(models.Model):    first_name = models.CharField(max_length=30)    last_name = models.CharField(max_length=30)class MyPerson(Person):    class Meta:        proxy = True    def do_something(self):        # ...        pass

MyPerson与基类Person操作同一个database table,也就是Person生成的database table.所以下述代码:

>>> p = Person.objects.create(first_name="foobar")>>> MyPerson.objects.get(first_name="foobar")<MyPerson: foobar>

也可以写一个区别于基类的排序方法,当调用proxy model时,对基类数据执行proxy model中的排序方法:

class OrderedPerson(Person):    class Meta:        ordering = ["last_name"]        proxy = True

可以理解为,基类model和proxy model 它们里边定义的方法都是独立的,没有继承关系,只是操作的数据对象是一个,就是基类model建立的database table,所以用哪个model访问就会返回对应的方法处理后的数据。例如,上例中OrderedPerson就会返回按照last_name排序的数据库。

同时,对于非abstract类型的基类,proxy model只能继承一个,不能继承自多个,因为proxy model不提供对于不同的database table中的rows的连接。对于abstract类型的基类,可以继承自多个,因为abstract类型的model本身并不没有建立database table.  proxy model 可以继承他们没有定义的来自非abstract的基类中的Meta的信息。

proxy 继承与 unmanaged models之间的区别:

1、可以设置Meta.managed=Flase,来指定model的fields. 也可以通过小心的设置Meta.db_table来创建一个unmanaged model, 来覆盖一个存在的model,并且加入一些python 的方法。但这样会特别琐碎和重复性。

2、对于proxy models来说,是继承了基类的managers,包括默认的manager。但是当涉及到multi-table继承时,proxy model子类并不继承基类的managers, 更多信息可参考 manager documentation 

当这两种特点放在一块考虑的时候,会把API变得异常复杂,所以总的原则如下:

1、如果想要给一个存在的model或者database table创建镜像,但是比不需要原始table的column的时候,可以令Meta.managed=False,此方法一般对于不再django控制下的database views 和 tables比较有用。

2、如果只是想改动以下python层面的一些行为方法,但是原始的field要保持不变,就令Meta.managed=True,这样,proxy model就会准确的拷贝了原model建立的database table及数据。

(这一部分没有理解好,留在后边学习)

 

多个继承

就像Python的继承一样,django也可以从多个基类来继承,但是注意,当多个基类中含有相同的名字的信息时(例如 Meta),子类只继承来自第一个基类的(Meta)。通常不需要而且不建议用多个基类来继承,因为当出现错误时,寻找起来会很困难。

不允许在子类中通过重新定义来隐藏基类的field

虽然在Python中可以这样,但是在django中会一直禁止这样做,例如在基类中有一个field name是author,则在子类中,禁止出现author的field name了。这个限制条件只是针对field的实例(即在django中),在Python中还是按照python的语法去写。所以说,如果用python语句去更改table当中column的名字,使子类与基类的某个column名字相同,例如都是author,这样是可以的,因为你是用python去写的,而不是在django中用model去生成的。如果在django中重写了基类中的field name, 会出现FieldError的错误。

 

django中Models的语法部分到此有了基础的理解了,本文以及django学习之Model(一)中的所有带有链接的地方,都是需要接下来学习。

加油!