在第4章中,我们介绍了Django的数据库层 - 如何定义模型以及如何使用数据库API创建,检索,更新和删除记录。 在本章中,我们将向您介绍Django这部分更高级的功能。
相关对象
回想一下第四章的书籍模型:
from django.db import models
class Publisher(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=50)
city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30)
country = models.CharField(max_length=50)
website = models.URLField()
def __str__(self):
return self.name
class Author(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=40)
email = models.EmailField()
def __str__(self):
return '%s %s' % (self.first_name, self.last_name)
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher)
publication_date = models.DateField()
def __str__(self):
return self.title
正如我们在第4章中所解释的那样,访问数据库对象上特定字段的值与使用属性一样简单。 例如,要确定ID为50的书的标题,我们将执行以下操作:
>>> from mysite.books.models import Book
>>> b = Book.objects.get(id=50)
>>> b.title
'The Django Book'
但是我们之前没有提到的一件事是相关的对象 - 表示为ForeignKey或者ManyToManyField的字段略有不同。
访问外键值
当你访问一个是ForeignKey的字段时,你会得到相关的模型对象。 例如:
>>> b = Book.objects.get(id=50)
>>> b.publisher
<Publisher: Apress Publishing>
>>> b.publisher.website
'http://www.apress.com/'
对于ForeignKey字段,它也是相反的,但是由于关系的非对称性,它有些不同。 要获取给定发布者的书籍列表,请使用publisher.book_set.all(),如下所示:
>>> p = Publisher.objects.get(name='Apress Publishing')
>>> p.book_set.all()
[<Book: The Django Book>, <Book: Dive Into Python>, ...]
在幕后,book_set只是一个QuerySet(如第4章所述),它可以像其他任何QuerySet一样进行过滤和切片。 例如:
>>> p = Publisher.objects.get(name='Apress Publishing')
>>> p.book_set.filter(title__icontains='django')
[<Book: The Django Book>, <Book: Pro Django>]
属性名称book_set通过将小写模型名称附加到_set来生成。
访问多对多的价值
多对多的值像外键值一样工作,除了我们处理QuerySet值而不是模型实例。 例如,以下是如何查看书籍的作者:
>>> b = Book.objects.get(id=50)
>>> b.authors.all()
[<Author: Adrian Holovaty>, <Author: Jacob Kaplan-Moss>]
>>> b.authors.filter(first_name='Adrian')
[<Author: Adrian Holovaty>]
>>> b.authors.filter(first_name='Adam')
[]
它也是相反的。 要查看作者的所有书籍,请使用author.book_set,如下所示:
>>> a = Author.objects.get(first_name='Adrian',
last_name='Holovaty')
>>> a.book_set.all()
[<Book: The Django Book>, <Book: Adrian's Other Book>]
在这里,与ForeignKey字段一样,属性名称book_set通过将小写模型名称附加到_set来生成。
管理者
在声明Book.objects.all()中,对象是查询数据库的一个特殊属性。 在第四章中,我们简单地把它看作模型的管理者。 现在是时候进一步深入了解管理者以及如何使用它们。
简而言之,模型的管理器是Django模型执行数据库查询的对象。 每个Django模型至少有一个管理器,您可以创建自定义管理器来定制数据库访问。 您可能需要创建自定义管理器的两个原因:添加额外的管理器方法,和/或修改管理器返回的初始QuerySet。
添加额外管理器方法
添加额外的管理器方法是将表级功能添加到模型的首选方法。 (对于行级功能 - 即作用于模型对象的单个实例的函数 - 使用模型方法,本章后面将对此进行解释。)
例如,让我们给Book模型一个管理器方法title_count(),它使用关键字并返回包含该关键字的标题的书籍数。 (这个例子稍微有些人性化,但它展示了管理者的工作方式。)
# models.py
from django.db import models
# ... Author and Publisher models here ...
class BookManager(models.Manager):
def title_count(self, keyword):
return self.filter(title__icontains=keyword).count()
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher)
publication_date = models.DateField()
num_pages = models.IntegerField(blank=True, null=True)
objects = BookManager()
def __str__(self):
return self.title Here are some notes about the code:
-
我们已经创建了一个扩展django.db.models.Manager的BookManager类。 这有一个方法,title_count(),它进行计算。 请注意,该方法使用self.filter(),其中self指的是管理器本身。
-
我们已经将BookManager()分配给模型上的objects属性。 这样做的效果是替换模型的“默认”管理器(称为对象),如果不指定自定义管理器,将自动创建该管理器。 我们称之为对象而不是别的,以便与自动创建的管理者保持一致。
有了这位管理,我们现在可以做到这一点:
>>> Book.objects.title_count('django')
4
>>> Book.objects.title_count('python')
18
显然,这仅仅是一个例子 - 如果你在交互提示中输入了这个值,你可能会得到不同的返回值。
为什么我们要添加一个诸如title_count()的方法? 封装通常执行的查询,以便我们不必重复代码。
修改初始管理器查询集
管理的基础QuerySet返回系统中的所有对象。 例如,Book.objects.all()返回书籍数据库中的所有书籍。 您可以通过重写Manager.get_queryset()方法来覆盖管理器的基本QuerySet。 get_queryset()应该返回一个QuerySet和你需要的属性。例如,以下模型有两个管理器 - 一个返回所有对象,一个返回Roald Dahl的书籍。
from django.db import models
# First, define the Manager subclass.
class DahlBookManager(models.Manager):
def get_queryset(self):
return super(DahlBookManager, self).get_queryset().filter(author='Roald Dahl')
# Then hook it into the Book model explicitly.
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=50)
# ...
objects = models.Manager() # The default manager.
dahl_objects = DahlBookManager() # The Dahl-specific manager.
使用这个示例模型,Book.objects.all()将返回数据库中的所有书籍,但是Book.dahl_objects.all()将仅返回由Roald Dahl编写的书籍。 请注意,我们明确地将对象设置为一个vanilla管理器实例,因为如果我们没有,唯一可用的管理器将是dahl_objects。 当然,因为get_queryset()返回一个QuerySet对象,所以可以使用filter(),exclude()和其他所有的QuerySet方法。 所以这些陈述都是合法的:
Book.dahl_objects.all()
Book.dahl_objects.filter(title='Matilda')
Book.dahl_objects.count()
这个例子还指出了另一个有趣的技术:在同一个模型上使用多个管理器。 您可以根据需要将多个Manager()实例附加到模型中。 这是为您的模型定义常见“过滤器”的简单方法。 例如:
class MaleManager(models.Manager):
def get_queryset(self):
return super(MaleManager, self).get_queryset().filter(sex='M')
class FemaleManager(models.Manager):
def get_queryset(self):
return super(FemaleManager, self).get_queryset().filter(sex='F')
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
sex = models.CharField(max_length=1,
choices=(
('M', 'Male'),
('F', 'Female')
)
)
people = models.Manager()
men = MaleManager()
women = FemaleManager()
这个例子允许你请求Person.men.all(),Person.women.all()和Person.people.all(),产生可预测的结果。 如果您使用自定义管理器对象,请注意第一个管理器
Django遇到(按照在模型中定义的顺序)具有特殊的状态。 Django将这个在类中定义的第一个Manager解释为“默认”管理器,而Django的多个部分(尽管不是管理应用程序)将专门为该模型使用该管理器。
因此,为了避免重写get_queryset()导致无法检索想要使用的对象的情况,小心选择默认管理器通常是一个好主意。
模型方法
在模型上定义自定义方法以将自定义行级功能添加到对象中。 尽管管理者打算做整个事情,但是模型方法应该在一个特定的模型实例上进行。 这是将业务逻辑保存在一个地方的一种有价值的技术 - 模型。
一个例子是解释这个最简单的方法。 以下是一些包含一些自定义方法的模型:
from django.db import models
class 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)
附录A中的模型实例参考有一个自动给每个模型的完整的方法列表。 你可以覆盖其中的大部分(见下面),但有几个你几乎总是想要定义:
-
str()。 一种Python“魔术方法”,它返回任何对象的Unicode“表示”。 这就是Python和Django在模型实例需要强制显示为纯字符串时所使用的内容。 最值得注意的是,当您在交互式控制台或管理中显示对象时,会发生这种情况。您总是要定义此方法; 默认不是很有帮助。
-
get_absolute_url()。 这告诉Django如何计算一个对象的URL。 Django在其管理界面中使用它,任何时候都需要找出对象的URL。 任何具有唯一标识它的URL的对象都应定义此方法。
覆盖预定义的模型方法
还有一组模型方法可以封装你想要定制的一堆数据库行为。 特别是,你经常想要改变save()和delete()的工作方式。 你可以自由地重写这些方法(和任何其他模型方法)来改变行为。 覆盖内置方法的经典用例是,如果您希望在保存对象时发生某些事情。 例如(请参阅save()以获取它接受的参数的文档):
from django.db import models
class 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()
你也可以防止保存:
from django.db import models
class 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.
记得调用超类方法 - 这就是超级(Blog,self).save(* args,** kwargs)业务 - 以确保对象仍然保存到数据库中。 如果您忘记调用超类方法,则默认行为不会发生,数据库也不会被触及。
传递可以传递给模型方法的参数也很重要 - 这就是* args,** kwargs位的作用。 Django将不时地扩展内置模型方法的功能,增加新的参数。 如果您在方法定义中使用* args,** kwargs,则可以保证您的代码在添加时自动支持这些参数。
网友评论