django的models数据模型的检索查询
一旦创建了数据模型,我们可以利用django给我们提供的数据库抽象接口API来实现对象的创建,检索,更新或删除操作,使用非常方便。本文前提有以下数据模型:
name = models.CharField(max_length=100)
tagline = models.TextField()
def __unicode__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField()
def __unicode__(self):
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog)
headline = models.CharField(max_length = 255)
body_text = models.TextField()
pub_date = models.DateField()
mod_date = models.DateField()
authors = models.ManyToManyField(Author)
n_comments = models.IntegerField()
n_pingbacks = models.IntegerField()
rating = models.IntegerField()
def __unicode__(self):
return self.headline
为了以python的方式来表示数据库表,django使用了一种可视化的系统:即model class表示一个数据库表,模型中一个类的实例表示数据库表中的一条记录。创建一个对象并实例化这个对象的过程就是给model class传递需要的关键参数,然后调用save()方法将其保存进数据库中。
前提是需要根据python的路径将所需要的数据模型导入,假设模型在文件mysite/blog/models.py中,如下是例子:
>>> b = Blog(name= 'Beatles Blog', tagline = 'All the latest Beatles news')
>>> b.save()
在后台django将完成一个insert的sql语句,django不会去操作数据库直至显式地调用了save(),并且save()无返回值
ForeignKey和ManyToManyField字段的save操作
更新一个ForeignKey字段的方式与保存一个正常的字段方式没有什么区别,简单的说,只需要通过右值赋值即可,下面例子中假如在数据库内已经保存着Entry和Blog实例数据,Entry的实例化对象entry内的blog属性被更新。
>>> entry = Entry.objects.get(pk=1)
>>> cheese_blog = Blog.objects.get(name="Cheddar Talk")
>>> entry.blog = cheese_blog
>>> entry.save()
>>> joe = Author.objects.create(name="Joe")
>>> entry.authors.add(joe)
>>> paul = Author.objects.create(name="Paul")
>>> george = Author.objects.create(name="George")
>>> ringo = Author.objects.create(name="Ringo")
>>> entry.authors.add(john,paul,george,ringo)
关于检索对象
为了从数据库中检索对象,django会在model class上通过Manager管理器创建一个QuerySet,这个QuerySet表示从数据库中收集到的一组对象,这组对象进行0个,1个甚至多个filter过滤器筛选操作,只要给这个filter传递需要的条件参数即可。从SQL角度来看,一个QuerySet相当于一个SELECT语句,过滤筛选filter相当于WHERE或者LIMIT.
通过使用模型的Manager管理器来获取QuerySet,每种model至少有一个Manager管理器,这个管理器也是"objects",可以直接通过model class来访问,如下:
<django.db.models.manager.Manager object at ...> #所以Manager也是一种object,通过model class访问
>>> b = Blog(name='Foo', tagline='Bar')
>>> b.objects
Traceback:
...
AttributeError: "Manager isn't accessible via Blog instances." #通过model class的实例不能访问Manager
对model来说,Manager管理器是产生QuerSet的主要方式,如Blog.objects.all()返回数据库中包含所有Blog对象的一组QuerySet。
1 检索所有的对象
最简单的方式获取表中的对象的方法是直接获取表中的所有对象,要达到这一目的,可能在Manager中使用all()来获取
- >>> all_entries = Entry.objects.all()
2 通过筛选器filter检索具体的对象
有时,我们仅需要所有对象中的某一个子集。为了能够获得这个子集,我们可以提取出一个初始的QuerySet,然后再增加一个筛选条件,有两种常用的方式来提取一个新的Query:
filter(**kwargs) 它返回一个新的QuerySet,并且它包含给定满足条件的对象
exclude(**kwargs) 它返回一个新的QuerySet,并且它包含与给定条件不满足的对象
至于筛选的条件参数,有一定的格式规则。下面会讲到。
如下,为了获取一个QuerySet,其包含来自2006年的blog entries,使用filter():
- Entry.objects.filter(pub_date__year=2006)
- Entry.objects.all().filter(pub_date__year=2006)
提取一个新QuerySet的结果还是一个QuerySet,所以可以采用将其连接起来,如下:
- >>> Entry.objects.filter(
- ... headline__startswith='What'
- ... ).exclude(
- ... pub_date__gte=datetime.date.today()
- ... ).filter(
- ... pub_date__gte=datetime(2005, 1, 30)
- ... )
(2) 筛选器过滤后产生的QuerySet是唯一的
每次你提取QuerySet后,会得到一个新的QuerySet,它不会与之前的QuerySet产生任何的关联,每次的提取操作都会生成一个独立且不同的QuerySet,它可以被保存,使用或复用。见如下例子:
- >>> q1 = Entry.objects.filter(headline__startswith='What')
- >>> q2 = q1.exclude(pub_date__gte=datetime.date.today())
- >>> q3 = q1.filter(pub_date__gte=datetime.date.today())
(3) QuerySet 是很懒的
什么意思呢?说QuerySet比较懒,主要原因是创建这些QuerySet它并没有对数据库的任何的行为,即它不会真正的去数据库检索,产生数据。你可以花一天的时候不停的创建,都不会对数据库有任何影响。而当QuerySet被用于其它运算时,django才会真正的进行数据库的检索工作。可以看下面的例子:
- >>> q = Entry.objects.filter(headline__startswith = 'What')
- >>> q = q.filter(pub_date__lte=datetime.date.today())
- >>> q = q.exclude(body_text__icontains="food")
- >>> <strong>print(q)</strong>
3 用get检索单个对象
filter()总是给出一个QuerySet,即使我们要匹配的对象只有一个,这时QuerySet只包含一个对象。如果我们事先已经知道要检索的对象只有一个时,可以在Manager管理器上使用get()方法来获取数据,它可以直接返回我们所需要的数据:
- >>> one_entry = Entry.objects.get(pk=1)
请注意一点,get()和filter()两者之间是有一点儿区别的,如果对象只有一个,在使用filter()时,实际上用的是切片操作[0],只获取其第一个元素。如果没有结果被匹配,get()将返回一个"DoesNotExist"异常,这个异常是model class的一个属性。即在上面代码中,如果没有在Entry中没有一个pk=1的对象,django会触发一个Entry.DoesNotExist.
类似地,如果用get()匹配的对象数据多于一个,django也会抱怨并触发一个MultipleObjectsReturned错误,它也是model class的其中一个属性。
4 其它检索方法
我们在大多数时候都是使用all(),get(),filter()和exclude()来从数据库中查找所需要的对象。但实际上还有其它更多的方法,这里不再说明,有需要再去查阅相关资料
5 限集QuerySets
可以使用python的切片操作来以修饰QuerySet成为一个有限集,这类似于SQL中的LIMIT以及OFFSET。如下例子,返回头5个对象:
- >>> Entry.objects.all()[:5]
- >>> Entry.objects.all()[5:10]
总的来说,用切片的方法对QuerySet进行操作将产生一个新的QuerySet,它也没有触发数据库的操作行为。但有一个特例就是,当在切片操作中使用了step,即“步长”时,会触发数据库的检索行为,如下所示,将从数据库中查到头10个对象当中的步长为2的对象:
- >>> Entry.objects.all()[:10:2]
- >>> Entry.objects.order_by("headline")[0]
- >>> Entry.objects.order_by("headline")[0:1].get()
field__lookuptype = value (注意:双下划线),如下例子:
- >>> Entry.objects.filter(pub_date__lte='2006-01-01')
在Django1.4中作出的更改:在查找条件中描述的字段必须是model中的字段,但是也有一个例外,即当那个字段是ForeignKey时,需要在添加后缀_id,在这种情况下,其条件的值必须是外键model中的主键值。如下例子:
- >>> Entry.objects.filter(blog_id__exact = 4)
下面列举一些常用的查找类型:
1 exact
- >>> Entry.objects.get(headline__exact="Man bites dog")<span style="font-family:Arial,Helvetica,sans-serif; background-color:rgb(255,255,255)"> </span><span style="font-family:Arial,Helvetica,sans-serif; background-color:rgb(255,255,255)"> </span>
如果没有给出exact,django默认将使用exact,如下:
- >>> Blog.objects.get(id__exact=14) # Explicit form
- >>> Blog.objects.get(id=14) # __exact is implied
大小写不敏感的匹配,故查询:
- >>> Blog.objects.get(name__iexact="beatles blog")
3 contains
大小写敏感的匹配包含关系,如下:
- Entry.objects.get(headline__contains="Lennon")
大小写不敏感的还有icontains.
4 startswitch, endswith
同理,也有大小写不敏感的istartswith, iendswith
Django中的Model继承
Django 中的 model 继承和 Python 中的类继承非常相似,只不过你要选择具体的实现方式:让父 model 拥有独立的数据库;还是让父 model 只包含基本的公共信息,而这些信息只能由子 model 呈现。
Django中有三种继承关系:
1.通常,你只是想用父 model 来保存那些你不想在子 model 中重复录入的信息。父类是不使用的也就是不生成单独的数据表,这种情况下使用抽象基类继承 Abstract base classes。
2.如果你想从现有的Model继承并让每个Model都有自己的数据表,那么使用多重表继承Multi-table inheritance。
3.最后,如果你只想在 model 中修改 Python-level 级的行为,而不涉及字段改变。 代理 model (Proxy models) 适用于这种场合。
Abstract base classes
如果你想把某些公共信息添加到很多 model 中,抽象基类就显得非常有用。你编写完基类之后,在 Meta 内嵌类中设置 abstract=True
,该类就不能创建任何数据表。然而如果将它做为其他 model 的基类,那么该类的字段就会被添加到子类中。抽象基类和子类如果含有同名字段,就会导致错误(Django 将抛出异常)。
1
2
3
4
5
6
7
8
9
|
class
CommonInfo(models.Model):
name
=
models.CharField(max_length
=
100
)
age
=
models.PositiveIntegerField()
class
Meta:
abstract
=
True
class
Student(CommonInfo):
home_group
=
models.CharField(max_length
=
5
)
|
sqlall结果:
1
2
3
4
5
6
|
CREATE TABLE
"myapp_student"
(
"id"
integer NOT NULL PRIMARY KEY,
"name"
varchar(
100
) NOT NULL,
"age"
integer unsigned NOT NULL,
"home_group"
varchar(
5
) NOT NULL
)
|
只为Student
model 生成了数据表,而CommonInfo不能做为普通的 Django model 使用,因为它是一个抽象基类。他即不生成数据表,也没有 manager ,更不能直接被实例化和保存。
对很多应用来说,这种继承方式正是你想要的。它提供一种在 Python 语言层级上提取公共信息的方式,但在数据库层级上,每个子类仍然只创建一个数据表,在JPA中称作TABLE_PER_CLASS。这种方式下,每张表都包含具体类和继承树上所有父类的字段。因为多个表中有重复字段,从整个继承树上来说,字段是冗余的。
Meta继承
创建抽象基类的时候,Django 会将你在基类中所声明的有效的 Meta 内嵌类做为一个属性。如果子类没有声明它自己的 Meta 内嵌类,它就会继承父类的 Meta 。子类的 Meta 也可以直接继承父类的 Meta 内嵌类,对其进行扩展。例如:
1
2
3
4
5
6
7
8
9
10
11
|
class
CommonInfo(models.Model):
name
=
models.CharField(max_length
=
100
)
age
=
models.PositiveIntegerField()
class
Meta:
abstract
=
True
ordering
=
[
'name'
]
class
Student(CommonInfo):
home_group
=
models.CharField(max_length
=
5
)
class
Meta(CommonInfo.Meta):
db_table
=
'student_info'
|
sqlall结果:
1
2
3
4
5
6
|
CREATE TABLE
"student_info"
(
"id"
integer NOT NULL PRIMARY KEY,
"name"
varchar(
100
) NOT NULL,
"age"
integer unsigned NOT NULL,
"home_group"
varchar(
5
) NOT NULL
)
|
按照我们指定的名称student_info生成了table。
继承时,Django 会对基类的 Meta 内嵌类做一个调整:在安装 Meta 属性之前,Django 会设置 abstract=False。 这意味着抽象基类的子类不会自动变成抽象类。当然,你可以让一个抽象类继承另一个抽象基类,不过每次都要显式地设置 abstract=True 。
对于抽象基类而言,有些属性放在 Meta 内嵌类里面是没有意义的。例如,包含 db_table 将意味着所有的子类(是指那些没有指定自己的 Meta 内嵌类的子类)都使用同一张数据表,一般来说,这并不是我们想要的。
小心使用 related_name (Be careful with related_name)
如果你在 ForeignKey 或 ManyToManyField 字段上使用 related_name 属性,你必须总是为该字段指定一个唯一的反向名称。但在抽象基类上这样做就会引发一个很严重的问题。因为 Django 会将基类字段添加到每个子类当中,而每个子类的字段属性值都完全相同 (这里面就包括 related_name)。注:这样使用 ForeignKey 或 ManyToManyField 反向指定时就无法确定是指向哪个子类了。
当你在(且仅在)抽象基类中使用 related_name 时,如果想绕过这个问题,就要在属性值中包含 '%(app_label)s' 和 '%(class)s'字符串。
1.'%(class)s'会被子类的名字取代。
2.'%(app_label)s'会被子类所在的app的名字所取代。
举例,在app common中,common/models.py:
1
2
3
4
5
6
7
8
9
10
11
|
class
Base(models.Model):
m2m
=
models.ManyToManyField(OtherModel, related_name
=
"%(app_label)s_%(class)s_related"
)
class
Meta:
abstract
=
True
class
ChildA(Base):
pass
class
ChildB(Base):
pass
|
在另外一个app中,rare/models.py:
1
2
|
class
ChildB(Base):
pass
|
那么common.ChildA.m2m字段的反向名称为common_childa_related, common.ChildB.m2m字段的反向名称为common_childb_related, rare app中rare.ChildB.m2m字段的反向名称为rare_childb_related.
如果你没有在抽象基类中为某个关联字段定义 related_name 属性,那么默认的反向名称就是子类名称加上 '_set',它能否正常工作取决于你是否在子类中定义了同名字段。例如,在上面的代码中,如果去掉 related_name 属性,在 ChildA 中,m2m 字段的反向名称就是 childa_set;而 ChildB 的 m2m 字段的反向名称就是 childb_set 。
多表继承(Multi-table inheritance)
这是 Django 支持的第二种继承方式。使用这种继承方式时,同一层级下的每个子 model 都是一个真正意义上完整的 model 。每个子 model 都有专属的数据表,都可以查询和创建数据表。继承关系在子 model 和它的每个父类之间都添加一个链接 (通过一个自动创建的 OneToOneField 来实现)。 例如:
1
2
3
4
5
6
7
|
class
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()
|
sqlall:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
BEGIN;
CREATE TABLE
"myapp_place"
(
"id"
integer NOT NULL PRIMARY KEY,
"name"
varchar(
50
) NOT NULL,
"address"
varchar(
80
) NOT NULL
)
;
CREATE TABLE
"myapp_restaurant"
(
"place_ptr_id"
integer NOT NULL PRIMARY KEY REFERENCES
"myapp_place"
(
"id"
),
"serves_hot_dogs"
bool
NOT NULL,
"serves_pizza"
bool
NOT NULL
)
;
COMMIT;
|
父类和子类都生成了单独的数据表,Restaurant中存储了Place的id,也就是通过OneToOneField链接在一起。继承关系通过表的JOIN操作来表示。在JPA中称作JOINED。这种方式下,每个表只包含类中定义的字段,不存在字段冗余,但是要同时操作子类和所有父类所对应的表。
Place 里面的所有字段在 Restaurant 中也是有效的,只不过数据保存在另外一张数据表当中。所以下面两个语句都是可以运行的:
1
2
|
>>> Place.objects.
filter
(name
=
"Bob's Cafe"
)
>>> Restaurant.objects.
filter
(name
=
"Bob's Cafe"
)
|
如果你有一个 Place,那么它同时也是一个 Restaurant, 那么你可以使用子 model 的小写形式从 Place 对象中获得与其对应的 Restaurant 对象:
1
2
3
4
|
>>> p
=
Place.objects.
filter
(name
=
"Bob's Cafe"
)
# If Bob's Cafe is a Restaurant object, this will give the child class:
>>> p.restaurant
<Restaurant: ...>
|
但是,如果上例中的 p 并不是 Restaurant (比如它仅仅只是 Place 对象,或者它是其他类的父类),那么在引用 p.restaurant 就会抛开Restaurant.DoesNotExist 异常:
1
2
3
4
|
>>>
from
myapp.models
import
Place,Restaurant
>>> p
=
Place.objects.create(name
=
'Place'
,address
=
'Place'
)
>>> p.restaurant
DoesNotExist: Place has no restaurant.
|
也就是说,创建Place实例的同时不会创建Restaurant,但是创建Restaurant实例的同时会创建Place实例:
1
2
3
4
|
>>>Restaurant.objects.create(name
=
'M'
,address
=
'M'
,serves_hot_dogs
=
True
,serves_pizza
=
True
)
<Restaurant: Restaurant
object
>
>>> Place.objects.get(name
=
'M'
)
<Place: Place
object
>
|
多表继承中的Meta (Meta and multi-table inheritance)
在多表继承中,子类继承父类的 Meta 内嵌类是没什么意见的。所有的 Meta 选项已经对父类起了作用,再次使用只会起反作用。(这与使用抽象基类的情况正好相反,因为抽象基类并没有属于它自己的内容)
所以子 model 并不能访问它父类的 Meta 内嵌类。但是在某些受限的情况下,子类可以从父类继承某些 Meta :如果子类没有指定 django.db.models.Options.ordering 属性或 django.db.models.Options.get_latest_by 属性,它就会从父类中继承这些属性。
如果父类有了排序设置,而你并不想让子类有任何排序设置,你就可以显式地禁用排序:
1
2
3
4
5
|
class
ChildModel(ParentModel):
# ...
class
Meta:
# Remove parent's ordering effect
ordering
=
[]
|
继承与反向关联(Inheritance and reverse relations)
因为多表继承使用了一个隐含的 OneToOneField 来链接子类与父类,所以象上例那样,你可以用父类来指代子类。但是这个 OnetoOneField 字段默认的 related_name 值与 django.db.models.fields.ForeignKey 和 django.db.models.fields.ManyToManyField 默认的反向名称相同。如果你与其他 model 的子类做多对一或是多对多关系,你就必须在每个多对一和多对多字段上强制指定 related_name 。如果你没这么做,Django 就会在你运行 验证(validate) 或 同步数据库(syncdb) 时抛出异常。
例如,仍以上面 Place 类为例,我们创建一个带有 ManyToManyField 字段的子类:
1
2
3
|
class
Supplier(Place):
# Must specify related_name on all relations.
customers
=
models.ManyToManyField(Restaurant, related_name
=
'provider'
)
|
指定链接父类的字段(Specifying the parent link field)
之前我们提到,Django 会自动创建一个 OneToOneField 字段将子类链接至非抽象的父 model 。如果你想指定链接父类的属性名称,你可以创建你自己的 OneToOneField 字段并设置 parent_link=True ,从而使用该字段链接父类。
代理model (Proxy models)
使用 多表继承(multi-table inheritance) 时,model 的每个子类都会创建一张新数据表,通常情况下,这正是我们想要的操作。这是因为子类需要一个空间来存储不包含在基类中的字段数据。但有时,你可能只想更改 model 在 Python 层的行为实现。比如:更改默认的 manager ,或是添加一个新方法。
而这,正是代理 model 继承方式要做的:为原始 model 创建一个代理(proxy)。你可以创建,删除,更新代理 model 的实例,而且所有的数据都可以象使用原始 model 一样被保存。不同之处在于:你可以在代理 model 中改变默认的排序设置和默认的 manager ,更不会对原始 model 产生影响。
声明代理 model 和声明普通 model 没有什么不同。设置Meta 内置类中 proxy 的值为 True,就完成了对代理 model 的声明。
举个例子,假设你想给 Django 自带的标准 User model (它被用在你的模板中)添加一个方法:
1
2
3
4
5
6
7
8
9
10
11
|
class
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
|
sqlall:
1
2
3
4
5
6
|
CREATE TABLE
"myapp_person"
(
"id"
integer NOT NULL PRIMARY KEY,
"first_name"
varchar(
30
) NOT NULL,
"last_name"
varchar(
30
) NOT NULL
)
;
|
MyPerson 类和它的父类 Person操作同一个数据表。特别的是,Person 的任何实例也可以通过 MyPerson 访问,反之亦然:
1
2
3
|
>>> p
=
Person.objects.create(first_name
=
"foobar"
)
>>> MyPerson.objects.get(first_name
=
"foobar"
)
<MyPerson: foobar>
|
你也可以使用代理 model 给 model 定义不同的默认排序设置。Django 自带的 User model 没有定义排序设置(这是故意为之,是因为排序开销极大,我们不想在获取用户时浪费额外资源)。你可以利用代理对 username 属性进行排序,这很简单:
1
2
3
4
|
class
OrderedPerson(Person):
class
Meta:
ordering
=
[
"last_name"
]
proxy
=
True
|
普通的 User 查询,其结果是无序的;而 OrderedUser 查询的结果是按 username 排序。
查询集只返回请求时所使用的 model (Querysets still return the model that was requested)
无论你何时查询Person 对象,Django 都不会返回 MyPerson 对象。针对 Person 对象的查询集只返回 Person 对象。代理对象的精要就在于依赖原始 User 的代码仅对它自己有效,而你自己的代码就使用你扩展的内容。不管你怎么改动,都不会在查询 Person 时得到 MyPerson。
基类的限制(Base class restrictions)
代理 model 必须继承自一个非抽象基类。你不能继承自多个非抽象基类,这是因为一个代理 model 不能连接不同的数据表。代理 model 也可以继承任意多个抽象基类,但前提是它们没有定义任何 model 字段。
代理 model 从非抽象基类中继承那些未在代理 model 定义的 Meta 选项。
代理 model 的 manager (Proxy model managers)
如果你没有在代理 model 中定义任何 manager ,代理 model 就会从父类中继承 manager 。如果你在代理 model 中定义了一个 manager ,它就会变成默认的 manager ,不过定义在父类中的 manager 仍是有效的。
继续上面的例子,你可以改变默认 manager,例如:
1
2
3
4
5
6
7
8
9
|
class
NewManager(models.Manager):
# ...
pass
class
MyPerson(Person):
objects
=
NewManager()
class
Meta:
proxy
=
True
|
如果你想给代理添加一个新的 manager ,却不想替换已有的默认 manager ,那么你可以参考 自定义 manager (custom manager) 中提到的方法:创建一个包含新 manager 的基类,然后放在主基类后面继承:
1
2
3
4
5
6
7
8
9
|
class
ExtraManagers(models.Model):
secondary
=
NewManager()
class
Meta:
abstract
=
True
class
MyPerson(Person, ExtraManagers):
class
Meta:
proxy
=
True
|
你可能不需要经常这样做,但这样做是可行的。
代理 model 与非托管 model 之间的差异(Differences between proxy inheritance and unmanaged models)
代理 model 继承看上去和使用 Meta 内嵌类中的 managed 属性的非托管 model 非常相似。但两者并不相同,你应当考虑选用哪种方案。
一个不同之处是你可以在 Meta.managed=False 的 model 中定义字段(事实上,是必须指定,除非你真的想得到一个空 model )。在创建非托管 model 时要谨慎设置 Meta.db_table ,这是因为创建的非托管 model 映射某个已存在的 model ,并且有自己的方法。因此,如果你要保证这两个 model 同步并对程序进行改动,那么就会变得繁冗而脆弱。
另一个不同之处是两者对 manager 的处理方式不同。这对于代理 model 非常重要。代理 model 要与它所代理的 model 行为相似,所以代理 model 要继承父 model 的 managers ,包括它的默认 manager 。但在普通的多表继承中,子类不能继承父类的 manager ,这是因为在处理非基类字段时,父类的 manager 未必适用。
我们实现了这两种特性(Meta.proxy和Meta.unmanaged)之后,曾尝试把两者结合到一起。结果证明,宏观的继承关系和微观的 manager 揉在一起,不仅导致 API 复杂难用,而且还难以理解。由于任何场合下都可能需要这两个选项,所以目前二者仍是各自独立使用的。
所以,一般规则是:
1.如果你要镜像一个已有的 model 或数据表,且不想涉及所有的原始数据表的列,那就令 Meta.managed=False。通常情况下,对数据库视图创建 model 或是数据表不需要由 Django 控制时,就使用这个选项。
2.如果你想对 model 做 Python 层级的改动,又想保留字段不变,那就令 Meta.proxy=True。因此在数据保存时,代理 model 相当于完全复制了原始 model 的存储结构。
多重继承(Multiple inheritance)
和 Python 一样,Django 的 model 也可以做多重继承。这里要记住 Python 的名称解析规则。如果某个特定名称 (例如,Meta) 出现在第一个基类当中,那么子类就会使用第一个基类的该特定名称。例如,如果多重父类都包含 Meta 内嵌类,只有第一个基类的 Meta 才会被使用,其他的都被会忽略。
一般来说,你没必要使用多重继承。
不允许"隐藏"字段(Field name "hiding" is not permitted)
普通的 Python 类继承允许子类覆盖父类的任何属性。但在 Django 中,重写 Field 实例是不允许的(至少现在还不行)。如果基类中有一个 author 字段,你就不能在子类中创建任何名为 author 的字段。
重写父类的字段会导致很多麻烦,比如:初始化实例(指定在 Model.__init__ 中被实例化的字段) 和序列化。而普通的 Python 类继承机制并不能处理好这些特性。所以 Django 的继承机制被设计成与 Python 有所不同,这样做并不是随意而为的。
这些限制仅仅针对做为属性使用的 Field 实例,并不是针对 Python 属性,Python 属性仍是可以被重写的。在 Python 看来,上面的限制仅仅针对字段实例的名称:如果你手动指定了数据库的列名称,那么在多重继承中,你就可以在子类和某个父类当中使用同一个列名称。(因为它们使用的是两个不同数据表的字段)。
如果你在任何一个父类中重写了某个 model 字段,Django 都会抛出 FieldError 异常。
django的models数据模型学习笔记
每个应用一般都会定义一个或多个models,这个数据models实际上是与数据库相关的,models中的每个属性都是数据库当中的一个字段,每个字段是数据库中的一个列。在models中定义的每个类相当于数据库当中的table.如
- class Musician(models.Model):
- First_name = models.CharField(max_length = 50 )
- Last_name = models.CharField(max_length = 50 )
- Instrument = models.CharField(max_length = 100 )
- class Album(models.Model):
- Artist = models.ForeignKey(Musician)
- Name = models.CharField(max_length = 100)
- Release_date = models.DateField()
- Num_starts = models.IntegerField()
如上所示,每个class的属性都是models中的某些字段类的实例,如Name是models.CharField的实例。Models中有许多的字段属性类。并且这些字段属性类均有一些参数,用于修饰这些class内的属性,如Name是CharField的实例,限定其最大字符为100字节.那么,这些字段属性类都有哪些参数呢?下面列出一些常用的参数:
null:若为true,dnango将在数据库相应的字段中允许存储null值,否则设为flase
blank: 与null有点类似,但是不同,它主要用于确认一些如表单中是否带有一些内容数据,如果blank设为true,则允许表单内容为空,否则设为flase.
choices:带此参数的字段一般为2维的列表或元组,类中的属性在定义时若含此参数,在页面上将显示一个select box框,其选项也被限定于choices给定的具体值,如下:
- YEAR__IN_SCHOOL_CHOICES = (
- (‘FR’, ‘Freshman’),
- (‘SO’, ‘Sophomore’),
- (‘JR’, ‘Junior’),
- (‘SR’, ‘Senior’),
- (‘GR’, ‘Graduate’),
- )
注:每个元组中的第一个参数如’FR’等将被存于数据库中,第二个参数将显示在select box中。对于一个给定的model对象实例,choices字段显示的值可以通过get_YOURFIELD_display方法来访问,如下:
- From django.db import models
- Class Person(models.Model):
- SHIRT_SIZES = (
- (‘S’, ‘Small’),
- (‘M’, ‘Medium’),
- (‘L’, ‘Large’),
- )
- name = models.CharField(max_length = 60 )
- shirt_size = models.CharField(max_length = 1, choices = SHIRT_SIZES)
- >>>p = Person(name =”zhm”, shirt_size = ‘L’)
- >>>p.save()
- >>>p.shirt_size
- u’L’
- >>>p.get_shirt_size_display()
- u’Large’
default: 字段的默认值,可以是个值或者是个callable的对象。如果是callable的,在每次新对象被创建时,它将被调用。
help_text:
primary_key: 设为true时为model的primary_key.若不设置,则系统会自动添加一个IntegerField字段为primary_key,每个model必须要有一个字段被设置成primary_key = true.
自动的主键字段:默认时,django会给每个model下面一个字段:
Id = models.AutoField(primary_key = true).这是一个自增的主键,如果要覆盖系统的主键字段,可以在model中的任何字段中设置primary_key = true即可。
Verbose field names(冗长的字段名称):
除了ForeignKey, ManyToManyField, OneToOneField外,其它每种字段类型如CharField均有一个可选的第一个固定参数:Verbose Name.如果没有给出这个参数值,django会自动的创建它,而自动创建的verbose name=字段实例名.如下:
- first_name = models.CharField(“person’s first name”, max_length = 30)
此时verbose name = “person’s first name”
而当
- first_name = models.CharField(max_length = 30)
它的verbose name = “first name”
另外,由于ForeignKey, ManyToManyField及OneToOneField其第一个参数应为一个model class,所以它的verbose name应该是显示给出:
- Poll = models.ForeignKey(Poll, verbose_name = “the related poll”)
- Sites = models.ManyToManyField(Site, verbose_name = “list of sites”)
- Place = models.OneToOneField(Place, verbose_name = “related place “)
Relationships
主要有三类:many-to-one, many-to-many, one-to-one
Many-to-one 关系
要定义一个多对一的关系,使用django.db.models.ForeignKey.其使用方法与其它字段类型一样,如CharField,没有任何区别,ForeignKey需要一个固定的参数,基于某个关系模型的类,举个例子,比如一辆汽车与工厂的关系,工厂可以创建许多的汽车,但是一辆汽车只能属于某一个工厂。所以可以用以下方式来创建:
- class Manufacturer(models.Model):
- #...
- class Car(models.Model):
- manufacturer = models.ForeignKey(Manufacturer)
- #...
汽车是由于某个工厂创建的,所以Car class中需要增加工厂的相关信息,这种关系表示为ForeignKey. 而它的第一个参数为Manufacturer,是一个model的名称,这个ForeignKey也可以接收其它参数,用于定义这些关系如何发生作用,不过这些参数都是可选的,以后自己可以查看相关资料。
Many-to-many关系
定义多对多关系可以使用ManyToMany字段类型。其使用方法与其它字段类型一致。与ForeignKey一样,它也要接受一个固定的参数,一个与之发生关系的model class的名称。可以举这么个例子来描述多对多的关系,比如说一个月饼上有许多的芝麻,而芝麻也可以存在于多个月饼之上,所以它们的关系就可以按以下方式来表示:
- class MoonCake(models.Model):
- #...
- class Sesame(models.Model):
- mooncakes = models.ManyToMany(MoonCake)
- #...
建议ManyToMany对象以复数形式来表达一组相关的多个model对象,如上的mooncakes.此外,ManyToMany可以在任意两个model中,这个没有任何关系,但是不能在两个模型内均存在,即上面的ManyToMany也可以定义在MoonCake内:sesames = models.ManyToMany(Sesame)
一般来说,ManyToMany实例应该定义在那些将要在表单(form)中被编辑的对象内。如上面的月饼与芝麻的关系中,我们更能想到的是,月饼拥有很多芝麻,而更少想到芝麻在多个月饼上,所以上面的关系是通过这种想法定义的,故在芝麻中定义ManyToMany.这样MoonCake表单可以让用户来选择那些芝麻。
更复杂的Many-to-many关系:
简单的表述就是一个人,它可以参加各种兴趣小组,即每种兴趣小组有许多的人员,这显然是多对多的关系,但是除此之外,还需要其它的额外的信息,比如关于人员加入小组的时期,加入的原因等,所以需要一个中间耦合的modle来进行连接。具体代码可以表述如下:
- class Person(models.Model):
- name = models.CharField(max_length = 128)
- def __unicode__(self):
- return self.name
- class Group(models.Model):
- name = models.CharField(max_length=128)
- members = models.ManyToMany(Person, through = ‘Membership’)
- def __unicode__(self):
- return self.name
- class Membership(models.Model):
- person = models.ForeignKey(Person)
- group = models.ForeignKey(Group)
- date_joined = models.DateField()
- invite_reason = models.CharField(max_length = 64)
请注意Group当中的”through”参数。当建立了一个中间model时,如上面的Membership,将显式的用外键foreignkey指定涉及到多对多关系的那些models.这些显式的声明定义了这两个models的关系是怎样的。下面来实际使用上面定义的模型:
- >>> ringo = Person.objects.create(name = ‘Ringo Starr”)
- >>> paul = Person.objects.create(name = “Paul McCartney”)
- >>> beatles = Group.objects.create(name = “The Beetles”)
- >>> m1 = Membership( person = ringo, group = beatles,
- ... date_joined = date(1962, 8, 16)
- ... invite_reason = “Needed a new drummer”)
- >>> m1.save()
- >>> beatles.members.all()
- [<Person: Ringo Starr>]
- >>> ringo.group_set.all()
- [<Group: The Beatles>]
- >>> m2 = Membership.objects.create(person = paul, group = beatles,
- ... date_joined = date(1960,8,1)
- ... invite_reason = “Wanted to form a band.”)
- >>> beatles.member.all()
- [<Person: Ringo Starr>],[<Person: Paul McCartney>]
与简单正常的多对多关系不一样,不能使用add, create或者赋值来创建这种关系对象,如:
- beatles.members.add(john)
- atles.members.create(name=”George Harrison”)
- Beatles.members = [join,paul,ringo, george]
以上这种方式对复杂的多对多关系是不可行的。这是因为直接创建创建Person与Group之间的对象关系无法表达其余额外的信息,这些信息需要由Membership模型来提供。所以创建这种多对多关系对象的唯一方式就是创建中间件model对象的实例。所以remove()方法也不适合于复杂的多对多对象,相应有一个替代的函数clear(),
- >>>beatles.members.clear()
这是可行的。
创建复杂多对多模型后的对象查询方式如下:
- >>> Group.objects.filter(members__name__startswith=’Paul’)
- [<Group: The Beatles>]
- >>> Person.objects.filter(group__name=’The Beatles’, membership__date_joined__gt = date(1961,1,1))
- [<Person: Ringo Starr>]
- >>> ringos_membership = Membership.objects.get(group = beatles, person=ringo)
- >>> ringos_membership.date_joined
- datetime.date(1962,8,16)
- >>> ringos_membership.invite_reason
- u’Needed a new drummer
- >>> ringos_membership = ringo.membership_set.get(group = beatles)
- >>> ringos_membership.date_joined
- datetime.date(1962,8,16)
- >>> ringos_membership.invite_reason
- u’Needed a new drummer
One-to-one关系
定义这种关系使用OneToOneField.需要一个固定参数:某个关系model的class,这种关系可以理解成继承的关系,比如创建了一个’place’的数据库,里面含有address, phone等标准的信息,如果你再创建一个‘餐馆‘数据库,它是建立在’place’数据库之上的,因为‘餐馆’也需要一些地址信息,所以将‘餐馆’用OneToOneField定义到’place’之上。
跨文件的models
可以引用来自另一个应用的model,只需要将另一个应用的model import进来,然后就可以使用了。见如下例子:
- from geography.models import ZipCode
- class Restaurant(models.Model):
- #...
- zip_code = models.ForeignKey(ZipCode)
字段名称的限制
1 不能是python保留的关键字
2 不能含有2个‘_’,如:
- Class Example(models.Model):
- foo__bar = models.IntegerField() #error
当然,django也是允许我们自定义字段类型,可以查阅相关文档。
Meta Options
通过使用内部嵌套的class Meta可以给定model的metadata,如下:
- Class ox(models.Model):
- Horn_length = models.IntegerField()
- Class Meta:
- ordering = [‘horn_length’]
- verbose_name_plural = “oxen”
此Meta的各种options有很多,可以查看相关资料。
Model methods
需要知道的经常会用到的一系列函数方法:
__unicode__()
返回unicode表示的对象,这是python与django使用的,当model实例需要被强迫或显示成文本字符时。
get_absolute_url():告诉django怎样计算对象的url,django一般在它的admin接口上使用,无论何时admin都需要知道一个对象的url.
覆盖预定义的model 方法
即可以定制一些model的方法,经常要改变的是save()和delete()工作。
当在保存一个对象时你希望要进行一些其它额外的操作时,可以改写成如下方式:
- Class Blog(models.Model):
- #....
- def save(self, *args, **kwargs):
- do_something()
- super(Blog, self).save(*args, **kwargs) # call the real save()method
- do_something_else()
Model继承
与python中正常的类继承差不多,但要分清的是你是否要让父类的model成为它各自独立的model(拥有各自独立的数据库表),还是要让父类保存通信的数据信息,这样就可以在子model中可视了。
在django中一共有三种方式的model继承:
1 父model用来保存信息,这样就不必要输入到子model中,所以这时父类不会以独立的方式使用,这种方式为抽象基类abstract base classes方式
2 如果你是现有model的子类(有可能来自另一个应用),需要让每个model都拥有它本身的数据库表,mutil-table多表继承采用的是这种方式
3 最后,如果你要修改python级的model, 而不改变model的各字段,你可以使用Proxy models方式。
Abstract base classes
当你需要把一些共享的信息放入某个model中时,使用抽象基类的继承方式是非常有用的。按正常的方式先写好model,然后增加clss Meta,将属性abstract设为true即可。然后对于其它的model,要继承于抽象基类,这样抽像model的字段会被添加到子类中。如下:
- class CommonInfo(models.Model):
- name = models.CharField(max_length = 100)
- age = models.PositiveIntegerField()
- class Meta:
- Abstract = True
- class Student(CommonInfo):
- home_group = models.CharField(max_length = 5)
这样Student就有三个字段:name, age, home_group,所以CommonInfo不可以用作正常的Django model,因为它只是一个抽象基类,它并不生成一个数据库,不能直接实例化或直接保存数据。所以这种抽象基类是非常有用的,它以python的方式提供了一种方法来提取出公共信息,同时仅当子model在创建数据库时才会创建相应的数据。
Meta的继承
当抽象基类被创建,django的做法是让基类内部的Meta子类作为一个可用的属性。如果子类没有声明它自己的Meta类型,这将从父类的Meta中继承过来,如果子类要扩展父类的Meta,可以再进行子类化,如下例子
- class CommonInfo(models.Model):
- name = models.CharField(max_length = 100)
- age = models.PositiveIntegerField()
- class Meta:
- abstract = True
- Ordering = [‘name’]
- class Student(CommonInfo):
- home_group = models.CharField(max_length = 5)
- class Meta(CommonInfo.Meta):
- db_table = ‘student_info’
Django的确对抽像基类中的Meta进行过一次修改,在子类进行安装Meta属性之前,它对abstract设置为false,这意味着子类不会自动的成为抽像基类,当然,如果我们自己想让子类成为抽像基类的话,可以显式地设置abstruct为true即可。
注:有时有些属性在将抽像类中的Meta类包含进来时会发生一些无法不好理解的行为。如包含db_table意味着所有的子类(不必描述他们自己的Meta)将使用基类所有的相同数据库表,这让子类也成为abstract了,这就不是我们想要的了。
小心related_name属性参数
若在ForeignKey或ManyToManyField中使用related_name属性,必须让字段名称具有唯一性。否则将会引起一些问题,因为这个字段将会被包含到各个子类中,而每次此属性字段都具有相同的值。
要解决这个问题,在使用related_name在抽像类中时,注意,仅仅针对抽像类,名称的部分要包含’%(app_label)s’和’%(class)s’。
1 ‘%(class)s’ 将会被使用这个字段的底层类的名称所代替。
2 ‘%(app_label)s’将会被子类所在的底层应用名称所代替。每个安装的应用都是唯一的,应用内的每个model名称也是唯一的。
如以下例子,给定一个应用app common/models.py:
- class Base(models.Model):
- m2m=models.ManyToManyField
- (OtherModel, related_name = “%(app_label)s_%(class)s_related”)
- class Meta:
- Abstract = True
- class ChildA(Base):
- Pass
- class ChildB(Base):
- Pass
接着还有另一个应用rare/models.py;
- from common.models import Base
- class ChildB(Base):
- pass
这样,ChindA当中的m2m名称将是common_childa_related, 同时ChindB中的m2m名称为common_childb_related,而另一应用rare中的childB中的m2m为rare_childb_related.这样可以保证唯一性。
注意:如果不指定related_name属性,系统会有默认的名称,可以查看相关资料。
Multi-table inheritance
Django支持的第二种model继承方式是继承父类的子model是各自独立的,每种模型与其数据库表相关联,可进行分别独立地创建与查询操作。这种继承关系将子model与它的每个父model进行了连接(django自动采用OneToOneField的方式进行连接),如下:
- class 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()
虽然数据保存在这两个不同的数据库表中,但Place中的所有字段在Restaurant中均可被访问,(也可以这么理解:Place主要存储所有类似于Restaurant各种商店的地址,Restaurant对象只能访问其本身在Place中存放的地址,而Place则可以访问所有商店的地址和名称),见如下例子:
- >>> Place.objects.filter(name=”Bob’s Cafe”)
- >>> Restaurant.objects.filter(name=”Bob’s Cafe”)
Place包含着一个Restaurant的地址,既然知道了Restaurant的地址,所以我们可以从Place对象访问到Restaurant对象,访问的方式是使用小写的Restaurant model名称如下:
- >>> p = Place.objects.get(id = 12) #先获取Restaurant对象的地址
- #如果p是一个Restaurant对象,即p是一个子类
- >>>.p.restaurant #请注意,是小写,这样可以直接访问Restaurant对象
- <Restaurant:....>
所以我们可以得出结论,有两种情况,
1如果p仅仅是Place的一条记录,且这条记录并不是Restaurant关联的,即这种记录是Place独有的。
2 若p这条记录不是Restaurant的地址,而是其它商店如Apple店的地址
在上述两种情况下通过p.restaurant访问则会出现问题了。
关于Mutil-table inheritance中的Meta
在这种多表继承中的情况下,如果子类从父类中继承它的Meta class,这种情况是不合理的。所有的Meta Option都是应用于它的父类,如果继承到子类中会产生一些冲突(这种情况与抽象基类相反,抽象基类并不存在这种方式)
因此子model并不访问它的父类的Meta class, 但是也有一些少许情况可以从父类继承,比如,如果子类并没有给出ordering属性或者是get_latest_by属性,那么这些属性将从他们的父类Meta class中继承。
但是如果父类具有ording且你不想从父类中继承它的ording,可以显式的将其设置为disable,如下:
- class ChildModel( ParentModel):
- ...
- class Meta:
- #remove parent’s ording effect
- Ordering = []
Inheritance and reverse relations
由于多表继承使用隐式的OneToOneField来连接父子model,如上例所示这可以让父model访问至子model,但是,如果对于使用了一些如ForeignKey和ManyToManyField关系的model而言,这些关键字(ForeignKey和ManyToManyField)所带的参数related_name的值是默认的,如果你想把这些关系继承至其它model的子类时,必须在这些字段中显示的提供related_name属性,如果我们不这么操作,django在校验validata或者syncdb操作时会引发一个error,如:
- Class Supplier(Place):
- #必须在所有的关系中说明related_name
- Customers = models.ManyToManyField(Restaurant, related_name = “provider”)
父model的连接字段说明
如上提到,如果在基于非抽象型数据关系model时,django将在父子model中自动的创建OneToOneField以连接两者,这样子model可以访问父model相应的字段,从父model中的对象也可以间接的访问子model的对象,但是如果在子model中想要更改父model中所提供的字段名称时,可以自己显式的用OneToOneField(在参数中添加设置parent_link = True)来创建这种关系,以连接至父model.
Proxy models
代理模型。代理model继承的目的在于为原始的model创建一个代理,我们可以进行创建,删除,更新代理模型的实例,并且操作的结果将会保存在原始模型中,即,通过代理摸型间接的操作了原始模型。区别在于通过代理模型,不需要修改原始模型就可以直接改变原始模型的一些东西,比如改变默认的model ordering或者默认的manager等。
代理model的声明与正常的model没什么区别,只是要告诉django它是一个proxy的,方式为在Meta中添加proxy属性为True.
例如,例设我们现在要对前面提到的Person model添加一个方法,可以如下操作:
- class MyPerson(Person):
- class Meta:
- Proxy = True
- def do_something(self):
- ...
上面定义的MyPerson model,对它实例的操作结果可以发生在它的父类Person身上,特别的,Person的新对象也可以通过MyPerson来访问,反之亦然:
- >>> p = Person.objects.create(first_name=”foobar”)
- >>> MyPerson.objects.get(first_name=”foobar”)
- <MyPerson: foobar>
我们可以通过代理模型对原模型进行不同方式的排序,如下,可以增加last_name属性进行排序:
- class OrderedPerson(Person):
- class Meta:
- ordering = [“last_name”]
- proxy = True
正常的原始Peson model的查询是无序的,但OrderedPerson查询后的排序是基于last_name来进行。我们不可能通过Person来查询并让django返回MyPerson对象。另外,用代理并不是说是以一种自己创建的模型来取代Person model的方式。
父类(基类的)一些约束
Proxy model必须从一个(注意:是一个)非抽象的model类中继承,不能从多个非抽象model中继承,因为proxy model并没有在不同的数据库表的多行之间提供一些连接,proxy model也可以从一些抽象基类中继承,前提条件是他们不能定义model字段。
Proxy model会从非抽象父类model中继承一些他们没有定义的Meta选项.
Proxy model managers
如果没有详细的给出proxy model的模型管理器(manager),那它将从父model中继承,如果在proxy model中定义了管理器,那么它将取代父model的管理器,成为默认的管理器,但是在父类中定义的这些管理器也还是可以访问的。
以下是上述的例子,当你查询Person model时你可以改变它的默认管理器:
- class NewManager(models.Manager):
- ...
- class MyPerson(Peson):
- objects = NewManager()
- class Meta:
- Proxy = True
如果我们想增加新的管理器至Proxy,而不会取代现有的默认管理器,可以使用相关文档描述的技术细节,它的主要方法是,创建一个包含新的管理器的基类,然后继承它,紧接在primary基类之后,如下:
- #为新管理器创建一个抽象类
- class ExtraManagers(models.Model):
- secondary = NewManager()
- class Meta:
- Abstract = True
- class MyPerson(Person, ExtraManagers): #紧接在Person之后
- class Meta:
- proxy = True;
我们可能不会经常这样做,但是有时也有需要的时候,所以这里先了解一下。
关于Multiple inheritance
与python的子类一样,Django的model可以从多个父model中继承,正常的python命名规则也同样适用,继承的第一个基类的Meta将被使用,其余基类的Meta将会被忽略。
一般来说,很少会从多个父model中继承,主要的使用原则就是越简单越好,这样可以避免总是去寻找一些特殊的来自其它类中的信息。
Models的字段名是不能被隐匿的
在正常的python继承情况下,它允许子类覆盖父类的一些属性,而在Django,这是不允许的,因为如果一个基类model有一个字段称为author,那么在你创建的另外一个model中,如果它继承了前面这个基类,则它是不能再创建一个名为author的字段。如果你覆盖一个父类当中的字段,django会产生一个FieldError错误。