4.1 定义模型
数据模型通常是Web应用程序的基础,从这里开始探索Django开发的细节。
Django的数据库模型层使用了大量的 ORM(对象关系映射),这一节
先解释下 Django的ORM,
然后深入讲解模型变量 model field
,
模型类model class
之间可能的关系,
以及通过模型类元数据model class metadata
来定义特定行为或者是激活和自定义 Django的 admin应用。
4.1.1 为什么使用ORM
Django和其他大多数现代Web框架一样,依赖一个强大的DAO,数据访问层,试图将底层的关系数据库和Python的面向对象的特质联系起来
使用ORM有4个充分的理由:
- 封装有用的方法
- 可移植性
- 安全性
- 表现力
4.1.1.1 封装有用的方法
Django模型对象是定义变量的首选方式,而变量通常对应的是数据库的列。
你可以请求一个 id 为 5 的Author对象并检查其 author.name
, 而不必去写SELECT name from authors WHERE id=5
这样的SQL语句, 这样要 Pythonic
的多。
而且,模型对象能给那个简陋的例子增加许多额外的价值,Django的ORM可以让你定义任何实例方法:
- 定义只读的变量或者属性集合,也称为 数据集合
data aggregation
或者计算属性calculated attribute
- Django的ORM允许重写内置的数据库修改方法,例如保存和删除对象
- 和编程语言集成(python)通常比较简单,可以让你的数据库对象和特定的接口或API更加一致
4.1.1.2 可移植性
ORM作为应用程序和数据库之间的代码层,通常具有很好的可移植性
4.1.1.3 安全性
使用ORM之后很少有机会需要自己执行SQL查询,所以不必担心保护性很差的查询字符串导致的问题,如SQL注入攻击等
而且ORM还提供一个智能化的引用和转义输入变量的核心机制,让你不在花很多时间来处理这种细枝末节
4.1.1.4 表现力
跟直接编写SQL相比,ORM最大的好处就是从数据库中获取记录时使用的查询语法。高级一点的语法不仅更容易编写,而且这种带入到Python领域的查询机制更带来了一系列有用的技巧和方法。比如,直接循环一个数据结构,原本在SQL中是相当笨拙的,现在却简洁的多,还可以回避本来是无可避免的讨厌的字符串操作。
4.1.2 Django丰富的变量类型
Django的模型拥有多种不同变量类型,一些跟它们在数据库里的实现比较接近,一些则是为Web表单界面而考虑设计
首先来看一个Django模型定义
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(Author)
length = models.IntergerField()
看来, Django用Python的类来表示对象,而对象则通常映射到SQL中的表,属性对应列;
这些属性本身也是对象, 是 Field 类的子类; 如上提到过的,
这些子类有的跟SQL列类型很相似, 其他的则都提供了某种程度的抽象。比如:
- CharField/TextField -- 都是用于保存文本,区别在于一个定长,一个无限长
- EmailField/URLField/IPAddressField -- 就是CharField加上验证功能
- BooleanField/NullBooleanField
- FileField/FilePathField -- FileField只在数据库中保存了一个文件的路径,和它接近的FileFieldPath
主键和唯一性
如果你没有明确指定,Django会自动生成主键 id(AutoField,自增整数)
如果你希望有更多的控制主键,只需要在某个变量上指定 primary_key = True,
这个变量会取代 id 成为这个表的主键
类似于SQL中的UNIQUE索引,Django也提供了unique=True
的参数
4.1.3 模型之间的关系
定义模型对象之间关系的能力通常是关系数据库最大的卖点之一, 同时也是
ORM之间相互竞争的区域。
Django目前的实现还是围绕着数据库展开,确保关系在数据库层而不是在应用层上定义;然而,由于SQL提供了一种显式组织关系的方法(外键),我们有必要再加入一些抽象来表示更复杂的关系。
4.1.3.1 外键(多对一)
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(Author)
Django的外键表现很直观,其主要参数就是它要引用的模型类;但是注意要把被引用的类放在前面。不过,如果不想留意顺序,也可以用字符串代替。
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey("Author")
#if Author class is defined in another file myapp/models.py
author = models.ForeignKey("myapp.Author")
class Author(models.Model):
name = models.CharField(max_length=100)
如果要引用自己为外键,可以设置 models.ForeignKey("self")
,
这在定义层次结构等类似场景很常用,比如Employee类可以具有类似supervisor或是hired_by这样的属性
外键ForeignKey只定义了关系的一端,但是另一端可以根据关系追溯回来,因为这是一种多对一的关系,多个子对象可以引用同一个父对象,而父对象可以访问到一组子对象。看下面的例子:
#取一本书“Moby Dick”
book = Book.objects.get(title="Moby Dick")
#取作者名字
author = Book.author
#获取这个作者所有的书
books = author.book_set.all()
这里从Author到Book的反向关系式通过Author.book_set
属性来表示的(这是一个manager对象),是由ORM自动添加的,
可以通过在 ForeignKey里指定 related_name
参数来改变它的名字。比如:
class Book(models.Model):
... ...
... ...
author = models.ForeignKey("Author", related_name = "books")
... ...
... ...
#获取这个作者所有的书
books = author.books.all()
对简单的对象层次来说,
related_name
不是必需的,但是更复杂的关系里,比如当有多个ForeignKey的时候就一定要指定了。
4.1.3.2 多对多
上面的例子假设的是一本书只有一个作者,一个作者有多本书,所以是多对一的关系;但是如果一本书也有多个作者呢?
这就是多对多的关系;由于SQL没有定义这种关系,必须通过外键用它能理解的方式实现多对多
这里Django提供了第二种关系对象映射变量ManyToManyField
语法上来讲, 这和 ForeignKey是一模一样的,你在关系的一端定义,把要关联的类传递进来,ORM会自动为另一端生成使用这个关系必要的方法和属性
不过由于 ManyToManyField的特性,在哪一端定义它通常都没有关系,因为这个关系是对称的
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
#获取一本书
book = Book.objects.get(title="Python Web Dev Django")
#获取该书所有的作者
authors = Book.author_set.all()
#获取第三个作者出版过的所有的书
books = authors[2].book_set.all()
ManyToManyField的秘密在于它在背后创建了一张新的表来满足这类关系的查询的需要,而这张表用的则是SQL外键,其中每一行都代表了两个对象的一个关系,同时包含了两端的外键
这张查询表在Django
ORM中一般是隐藏的,不可以单独查询,只能通过关系的某一端查询;不过可以在
MTMField上指定一个特殊的选项through
来指向一个显式的中间模型类,更方便你的手动管理关系的两端
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author, through = "Authoring")
class Authoring(models.Model):
collaboration_type = models.CharField(max_length=100)
book = model.ForeignKey(Book)
author = model.ForeignKey(Author)
查询Author和Book的方法和之前完全一样,另外还能构造对authoring
的查询
chan_essay_compilations = Book.objects.filter(
author__name__endswith = 'Chun'
authoring__collaboration_type = 'essays'
)
如此,Django在创建关系的能力上就显得更灵活了
4.1.3.3 用一对一关系进行组合
类似的,Django提供了OneToOneField
属性,几乎和ForeignKey
一样,接受一个参数(要关联的类或者"self"),同样也接受一个可选参数related_name
,这样就可以在两个相同的类里区分出多个这样的关系来。
不同的是,OTOField没有在反向关系中添加reverse manager
,而只是增加了一个普通属性而已,因为关系的另一端一定只有一个对象。
这种关系最常用的是用来支持对象组合或者是拥有关系,所以相比现实世界,它更加面向对象一点。
在Django直接支持模型继承(model inheritance)之前,OTOField主要是用来实现模型继承, 而现在,则是转向对这个特性的幕后支持了。
限制关系
关于定义关系的最后一点, ForeignKey 和 MTMField 都可以指定一个>limit_choices_to
参数,这个参数接受一个字典,键值对是查询的关键字和值
class Author(models.Model):
name = models.CharField(max_length=100)
class SmithBook(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author, limit_choices_to={
'name__endswith' : 'Smith'
})
这个例子中,Book模型就只能和姓Smith的Authors类一起工作。当然这个问题最好用另一种解决方案--ModelChoiceField,ModelMultipleChoiceField
4.1.3.4 模型继承
Django 的ORM中一个相对新的特性就是 模型继承 model inheritance;
两个模型之间除了外键以及其他关系之外,还可以和普通的、非ORM的Python类一样,通过从另一个模型继承来定义模型。
模型继承,子类通过添加或者是重写变量来和父类区分开来,而不需要重写整个类的定义。
Django目前支持2种不同的继承方式,每种都有自身的优缺点:
- abstract base class 抽象基础类 -- 纯Python的继承
- multi-table inheritance 多表继承
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=100)
genre = models.CharField(max_length=100)
num_pages = models.IntergerField()
authors = models.ManyToManyField(Author)
def __unicode__(self):
return self.title
class Meta:
abstract = True
class SmithBook(Book):
authors = models.ManyToManyField(Author, limit_choices_to = {
'name_endswith': 'Smith'
})
这个例子,抽象基础类方式继承,纯Python的继承, 允许重构Python模型的定义,这样变量和方法都可以从基类中继承下来。然而在数据库和查询层上并没有基类的概念,子类在数据库的表其实还是复制了基类的一个变量,而不是为基类创建一张额外的数据表
另外,这里代码的关键是 abstract = True
设置, 指明了Book是一个抽象基础类,只是用来为它实际的模型子类提供属性而存在的。
再说说多表继承, 同样,还是会用到Python的 类继承, 但是不再需要 abstract = True
这个 Meta类选项了。
在检查模型实例或是查询的时候,多表继承和前面看到的一样,子类会从父类中继承所有的属性和方法
主要的区别在于,底层的机制。在这儿,父类是拥有自己数据表的完整的Django模型,可以正常的实例化,同时还能把自己的属性借给子类,其实,这是通过自动在子类和父类之间设置了一个OneToOneField,以及幕后一些小手段把两个对象连在一起实现的,所以,子类才能继承父类的属性。
因此,多表继承其实就是对普通的has-a
关系(或者说对象组合
)的一个方便的包装
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=100)
genre = models.CharField(max_length=100)
num_pages = models.IntegerField()
authors = models.ManyToManyField(Author)
def __unicode__(Book):
return self.title
class SmithBook(Book):
authors = models.ManyToManyField(Author, limit_choices_to={
'name_endswith':'Smith'
})
这里唯一的不同是,没有了Meta abstract 选项。那结果是什么呢?
在一个空数据库和这个models.py
文件上运行 manage.py syncdb
会创建三张表 Author, Book, SmithBook
而抽象基础类的情况下,只创建了 Author, SmithBook 两张表。
注意 SmithBook 实例得到的 book_ptr
属性会指向和他们组合的Book实例,而属于SmithBook的Book实例会得到一个 smithbook
属性。
之所以说多表继承更适合我们这个Smithbook的例子,因为我们可以同时实例化普通的Book对象以及SmithBook对象。而实际应用过程中,也是多表继承要比抽象基础类好用的多。
4.1.3.5 Meta嵌套类
模型里定义的变量fields
和关系relationships
提供了数据库的布局以及稍后查询模型时要用的变量名--经常你还需要添加__unicode__ 和 get_absolute_url 方法或是重写 内置的 save 和 delete方法。
然而,模型的定义还有第三个方面--告知Django关于这个模型的各种元数据信息的嵌套类Meta
Meta类处理的是模型的各种元数据的使用和显示:
- 比如在一个对象对多个对象是,它的名字应该怎么显示;
- 查询数据表示默认的排序顺序是什么?
- 数据表的名字是什么
- 多变量唯一性 (这种限制没有办法在每个单独的变量声明上定义)
class Person(models.Model):
first = models.CharField(max_length=100)
last = models.CharField(max_length=100)
middle = models.CharField(max_length=100, blank=True)
class Meta:
ordering = ['last', 'first', 'middle']
unique_together = ['first', 'last', 'middle']
#Django默认的复数形式是加 s,这里不适用
verbose_name_plural = "people"
4.1.3.6 Admin注册和选项
2 使用模型
2.1 用manage.py创建和更新数据库
2.1.1 syncdb
创建所有的应用程序所需要的数据表
并不是对整个DB进行一次完整的同步,只是保证所有的模型类都有对应的数据表,在必要的时候创建为模型创建新的表,但是不会修改已经存在的表
因此,如果你创建模型,运行syncdb将它载入DB,然而改变这个模型时,syncdb不会试图去和DB协调这些改动,需要程序员自己去
- 手动修改
- 通过脚本修改
- 直接删掉数据表或者整个数据库然后哦重新执行syncdb
原创文章:http://www.cnblogs.com/ganiks
2.1.2 sql*
- sql
显示 CREATETABLE调用
- sqlall
跟sql一样从.sql文件中初始化数据载入语句
- sqlindexes
显示对主键创建索引的调用
- sqlclear
显示Drop Table调用
- sqlreset
sqlclear和sql的组合
显示DROP+CREATE
- sqlcustom
执行应用程序自定义initial SQL
2.1.3 loaddata
载入初始数据, 和sqlcustom类似,但是没有原始SQL
2.1.4 dumpdata
把现有数据库里的数据输出为JSON、XML格式
2.2 查询
2.2.1 Manager类
总是附在模型类里,除非有特别指定,每个模型类都会展示一个objects属性,它构成了这个模型在数据库所有的基本查询
Manager是从数据库获取信息的门户
- all
返回一个包含模式里所有数据库记录的QuerySet
- filter
返回一个包含符合指定条件的模型记录的QuerySet
- exclude
和filter相反--查找不符合条件的那些记录
- get
获取单个符合条件的记录(没找到或者是>1个记录都将抛出异常)
2.2.2 QuerySet类
模型类实例的列表,或者说是数据库行记录的列表
2.2.3 查询语法
将QuerySet作为数据库查询
books_about_trees = Book.objects.filter(title__contain="Trees")
SELECT * FROM myapp_book WHERE title LIKE "%Tree%"
john_does = Person.objects.filter(last="Doe", first="John")
everyone = Person.objects.all()
之前提到的Meta嵌套类里定义的哪些各种和查询相关的选项都会影响所生成的SQL
ordering->ORDER BY
QuerySet被当做一个数据库查询的始发端,接受动态的关键字参数然后转化成合适的SQL
- contain
- gt
- get
- in
- ... ...
将QuerySet作为容器
QuerySet像一个列表,实现了一部分列表的接口
- 迭代 for record in queryset:
- 索引 queryset[0]自动转换成SQL的 OFFSET
- 切片 queryset[:5] 自动转换成SQL 的LIMIT
- 获取长度 len(queryset)
- ... ...
最好不要把QuerySet通过list函数转换成一个真正的列表,因为如果QuerySet很大的话,会导致内存或者数据库很大的负担
QuerySet是懒惰的,只有在必要时才会去执行数据库查询
- 当被转换成list迭代、索引、切片、获取长度操作时
-
组合QuerySet查询
Person.objects.filter(last="Doe").filter(first="John").filter(middle="Quincy")
overdue_books = book_queryset.filter(due_data_lt=datetime.now())
nonfictionsmithBook.objects.fitler(author__last="Smith").exclude(genre="Fiction")
查询结果排序
all_sorted_first = Person.objects.all().order_by('first')
all_sorted_first_five = Person.objects.all().order_by('first')[:5]
sorted_by_state = Person.objects.all().order_by('address__state', 'last')
Person有一个FK包含了一个Address, Address中包含一个state变量,这里希望按照state,再按照姓氏排序
其他改变查询的方法
- order_by
- reverse
- distinct
- values
Person.objects.values('first')
[{'first': u'John'}, {'first': u'Jane'}]
- values_list
Person.objects.values_list('last')
[(u'Doe',),(u'Doe',)]
- select_related
Person.objects.all().select_related('address', depth=1)
- 用Q和~Q组合查询关键字
specific_does = Person.objects.filter(last="Doe").exclude(Q(first="John")|Q(middle="Quincy"))
specific_does = Person.objects.filter(Q() | (Q() & Q() &~Q()))
-
用Extra调整SQL
- select 修改SELECT语句
- where 提供额外的WHERE子句
- tables 提供额外的表
- params 安全的替换动态参数
- select 修改SELECT语句
2.3 利用Django没有提供的SQL特性
2.3.1 定义模式schema和定制initial SQL
通过initial SQL,将模式定义命令存放在Django项目中,当Django的工具来创建或者重新创建DB时,*.sql会被包含近来
myproject/myapp/sql/triggers.sql
- 视图
- 触发器和联级
- 自定义函数和数据类型
2.3.2 Fixtures: 载入和导出数据
类似initial SQL,每个Django项目有一个fixtures目录,里面存放XML YAML 或者JSON数据文件,在运行数据库创建或者重设命令的时候会运行
这个特性的主要作用在于比如要将数据从PostgreSQL导出数据和导入数据
myproject/myapp/fixtures/initial_data.json
2.3.3 自定义SQL查询
导入django.db中定义的connection对象,获取数据库游标进行查询
cursor = connection.cursor()
cursor.execute("SELECT first, last FROM myapp_person WHERE last='Doe'")
doe_rows = cursor.fetchall()
本文结束。
附件:
思维导图全图:http://images.cnblogs.com/cnblogs_com/ganiks/618830/o_%E6%A8%A1%E5%9E%8B.JPG
作者:ganiks
出处:http://www.cnblogs.com/ganiks/p/django-define-and-use-model.html