Python 学习笔记

先进的Django模型

2018-02-09  本文已影响5人  大爷的二舅

在第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:
  1. 我们已经创建了一个扩展django.db.models.Manager的BookManager类。 这有一个方法,title_count(),它进行计算。 请注意,该方法使用self.filter(),其中self指的是管理器本身。

  2. 我们已经将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中的模型实例参考有一个自动给每个模型的完整的方法列表。 你可以覆盖其中的大部分(见下面),但有几个你几乎总是想要定义:

覆盖预定义的模型方法

还有一组模型方法可以封装你想要定制的一堆数据库行为。 特别是,你经常想要改变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,则可以保证您的代码在添加时自动支持这些参数。

上一篇 下一篇

猜你喜欢

热点阅读