先进的Django模型

作者: 大爷的二舅 | 来源:发表于2018-02-09 19:53 被阅读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中的模型实例参考有一个自动给每个模型的完整的方法列表。 你可以覆盖其中的大部分(见下面),但有几个你几乎总是想要定义:

    • 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,则可以保证您的代码在添加时自动支持这些参数。

    相关文章

      网友评论

        本文标题:先进的Django模型

        本文链接:https://www.haomeiwen.com/subject/qxsqtftx.html