2.4 模型高级应用 第二章:模型 (Models) 与数据库 2.4 模型高级应用 在 Django 的模型世界中,我们不仅仅局限于定义简单的字段和关系。模型的高级应用旨在赋予模型更强大的能力,使其能够更好地服务于业务逻辑,提升代码的复用性和可维护性。本章节将深入探讨模型的高级特性,助您构建更加健壮和灵活的 Django 应用。 2.4.1 模型方法 (Model Methods):定制模型行为 模型方法是定义在模型类内部的普通 Python 方法。它们赋予了模型对象自定义行为的能力,使得我们可以在模型层面封装业务逻辑,提高代码的内聚性。模型方法可以访问模型实例的属性 (字段值),并执行各种操作,例如数据处理、业务规则验证、状态更新等。 1.
在 Django 的模型世界中,我们不仅仅局限于定义简单的字段和关系。模型的高级应用旨在赋予模型更强大的能力,使其能够更好地服务于业务逻辑,提升代码的复用性和可维护性。本章节将深入探讨模型的高级特性,助您构建更加健壮和灵活的 Django 应用。
模型方法是定义在模型类内部的普通 Python 方法。它们赋予了模型对象自定义行为的能力,使得我们可以在模型层面封装业务逻辑,提高代码的内聚性。模型方法可以访问模型实例的属性 (字段值),并执行各种操作,例如数据处理、业务规则验证、状态更新等。
1. __str__ 方法:人性化的对象表示
__str__ 方法是 Python 的魔术方法之一,用于定义对象的字符串表示形式。在 Django 模型中,当我们打印模型实例或者在 Django Admin 等界面显示模型对象时,默认会调用 __str__ 方法。通过自定义 __str__ 方法,我们可以返回更具可读性和信息量的对象描述。
from django.db import models class Product(models.Model): name = models.CharField(max_length=100) price = models.DecimalField(max_digits=10, decimal_places=2) def __str__(self): return f"{self.name} - ${self.price}"
在上述例子中,Product 模型的 __str__ 方法返回了产品名称和价格的组合字符串。这样,在 Django Admin 或者模板中显示 Product 对象时,会显示类似 "Product A - $19.99" 这样的信息,而不是默认的 <Product: Product object (1)>。
2. 自定义业务逻辑方法
除了 __str__ 这样的魔术方法,我们还可以定义自定义的模型方法来封装特定的业务逻辑。例如,对于一个 Order 模型,我们可以定义一个 calculate_total 方法来计算订单总额。
from django.db import models class Order(models.Model): order_date = models.DateField() customer_name = models.CharField(max_length=100) def calculate_total(self): total = 0 for item in self.orderitem_set.all(): # 假设 OrderItem 是订单项模型,通过外键关联到 Order total += item.quantity * item.product.price return total class OrderItem(models.Model): order = models.ForeignKey(Order, on_delete=models.CASCADE) product = models.ForeignKey(Product, on_delete=models.CASCADE) quantity = models.IntegerField()
在模板或视图中,我们可以直接调用 order_instance.calculate_total() 来获取订单总额,将计算逻辑封装在模型内部,提高了代码的可维护性和复用性。
3. 模型方法的应用场景
数据处理和转换: 例如,将多个字段的值组合成一个新的属性,或者对字段值进行格式化处理。
业务规则验证: 在模型层面进行更复杂的业务规则验证,例如检查订单是否满足最低消费金额。
状态更新: 模型对象状态的变更操作,例如订单状态的更新 (待付款 -> 已付款 -> 已发货)。
辅助属性计算: 计算模型的衍生属性,例如用户的年龄、订单的平均价格等。
模型管理器是 Django 模型中负责处理数据库查询操作的组件。每个 Django 模型默认都有一个名为 objects 的管理器,它是 django.db.models.Manager 类的实例。通过自定义模型管理器,我们可以:
定制默认 QuerySet: 修改默认的查询集,例如默认只返回活跃状态的对象。
添加自定义查询方法: 在管理器上定义新的方法,用于执行特定的数据库查询操作,提高查询代码的复用性。
1. 自定义管理器:创建 Manager 子类
要创建自定义管理器,我们需要创建一个继承自 django.db.models.Manager 的子类,并在模型类中指定使用自定义管理器。
from django.db import models class ActiveProductManager(models.Manager): def get_queryset(self): return super().get_queryset().filter(is_active=True) # 默认只返回 is_active=True 的产品 def expensive_products(self): return self.get_queryset().filter(price__gt=100) # 获取价格大于 100 的活跃产品 class Product(models.Model): name = models.CharField(max_length=100) price = models.DecimalField(max_digits=10, decimal_places=2) is_active = models.BooleanField(default=True) objects = models.Manager() # 默认管理器 active_objects = ActiveProductManager() # 自定义管理器
在上述例子中,我们创建了一个 ActiveProductManager,重写了 get_queryset 方法,使其默认只返回 is_active=True 的产品。同时,我们还定义了一个 expensive_products 方法,用于获取价格大于 100 的活跃产品。
在模型类中,我们保留了默认的 objects 管理器,并新增了一个 active_objects 管理器。现在,我们可以通过 Product.objects.all() 获取所有产品,通过 Product.active_objects.all() 获取所有活跃产品,通过 Product.active_objects.expensive_products() 获取价格大于 100 的活跃产品。
2. 修改默认管理器:重写 objects
如果我们希望将自定义管理器设置为模型的默认管理器,可以直接将自定义管理器实例赋值给模型的 objects 属性。
class Product(models.Model): # ... (字段定义) ... objects = ActiveProductManager() # 将 ActiveProductManager 设置为默认管理器
这样,当我们使用 Product.objects.all() 时,实际上会调用 ActiveProductManager 的 get_queryset 方法,默认只返回活跃产品。如果需要访问所有产品,可以再定义一个新的管理器,例如 all_products = models.Manager()。
3. 管理器方法与 QuerySet 方法的区别
管理器方法通常返回 QuerySet 对象或者单个模型实例,而 QuerySet 方法则是在已有的 QuerySet 对象上进行链式操作,进一步筛选或操作数据。管理器方法是 QuerySet 操作的入口点,用于创建初始的 QuerySet 对象。
4. 模型管理器的应用场景
数据过滤和筛选: 定义不同场景下的数据筛选条件,例如活跃用户、已发布文章等。
复杂查询封装: 将复杂的数据库查询逻辑封装在管理器方法中,简化视图和模板中的查询代码。
性能优化: 通过自定义管理器,可以编写更高效的数据库查询,例如使用 prefetch_related 或 select_related 进行关联查询优化。
模型继承允许我们创建基于现有模型的新模型,实现代码的复用和模型的层次化组织。Django 提供了三种模型继承方式:抽象基类、多表继承和代理模型。
1. 抽象基类 (Abstract Base Classes)
抽象基类是一种用于代码复用的机制,它本身不会在数据库中创建表。抽象基类中定义的字段和方法会被子类继承,子类会在数据库中创建独立的表,并包含从抽象基类继承的字段。
from django.db import models class CommonInfo(models.Model): name = models.CharField(max_length=100) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: abstract = True # 声明为抽象基类 class Product(CommonInfo): # 继承自抽象基类 price = models.DecimalField(max_digits=10, decimal_places=2) class Category(CommonInfo): # 继承自抽象基类 description = models.TextField()
在上述例子中,CommonInfo 被声明为抽象基类,它定义了 name, created_at, updated_at 等通用字段。Product 和 Category 模型继承自 CommonInfo,它们会自动拥有这些通用字段,同时还可以定义各自特有的字段。数据库中只会创建 product 和 category 表,而不会创建 commoninfo 表。
2. 多表继承 (Multi-table Inheritance)
多表继承会在数据库中为父类和子类分别创建独立的表。子类表通过一个一对一的外键关联到父类表。当查询子类对象时,Django 会自动进行 JOIN 查询,将父类表和子类表的数据合并。
from django.db import models class Place(models.Model): name = models.CharField(max_length=50) address = models.CharField(max_length=80) class Restaurant(Place): # 继承自 Place serves_hot_dogs = models.BooleanField(default=False) serves_pizza = models.BooleanField(default=False)
在多表继承中,Place 和 Restaurant 模型都会在数据库中创建表。restaurant 表会有一个指向 place 表的一对一外键 place_ptr_id。
3. 代理模型 (Proxy Models)
代理模型是一种特殊的模型继承方式,它不会创建新的数据库表,而是作为现有模型的代理。代理模型可以修改模型的 Python 行为,例如默认管理器、方法等,但不会改变数据库结构。
from django.db import models class Person(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) class Manager(Person): # 代理模型 class Meta: proxy = True def is_staff(self): return True # 代理模型可以添加新的方法
在代理模型中,Manager 模型不会创建新的数据库表,它仍然使用 person 表。但是,我们可以在 Manager 模型中添加新的方法或者自定义管理器,从而修改模型的行为。
4. 模型继承的选择
抽象基类: 适用于提取通用字段和方法,实现代码复用,但不希望创建父类表的场景。
多表继承: 适用于模型之间存在 "is-a" 关系,并且父类和子类需要独立存储数据,或者子类需要扩展父类字段的场景。
代理模型: 适用于修改现有模型的 Python 行为,例如添加方法、自定义管理器,但不希望修改数据库结构的场景。
每个 Django 模型类内部都可以定义一个 Meta 内部类,用于配置模型的元数据,精细控制模型的各种行为,例如表名、排序、索引、权限等。
常用的 Meta 选项:
db_table: 自定义模型在数据库中对应的表名。默认情况下,Django 会根据应用名和模型名自动生成表名。
class Product(models.Model): # ... class Meta: db_table = 'product_info' # 自定义表名为 product_info
ordering: 定义模型对象的默认排序方式。可以指定字段名 (加负号表示倒序) 或者方法名。
class Product(models.Model): # ... class Meta: ordering = ['-price', 'name'] # 默认先按价格倒序,再按名称正序排序
unique_together: 定义多个字段的联合唯一约束,确保这些字段的组合值在表中是唯一的。
class OrderItem(models.Model): order = models.ForeignKey(Order, on_delete=models.CASCADE) product = models.ForeignKey(Product, on_delete=models.CASCADE) quantity = models.IntegerField() class Meta: unique_together = ('order', 'product') # 同一个订单中,产品不能重复
index_together: 定义多个字段的联合索引,提高多字段组合查询的性能。
class Order(models.Model): order_date = models.DateField() customer_name = models.CharField(max_length=100) class Meta: index_together = ('order_date', 'customer_name') # 为 order_date 和 customer_name 创建联合索引
verbose_name 和 verbose_name_plural: 定义模型在 Django Admin 和表单中显示的友好名称 (单数和复数形式)。
class Product(models.Model): # ... class Meta: verbose_name = '产品' verbose_name_plural = '产品列表'
abstract: 声明模型为抽象基类 (如前所述)。
managed: 控制 Django 是否负责管理模型的数据库表 (创建、删除、迁移等)。默认为 True。如果设置为 False,Django 不会管理该模型的表,通常用于管理已存在的数据库表。
permissions: 自定义模型的权限。
class Product(models.Model): # ... class Meta: permissions = [ ('view_product_detail', 'Can view product detail'), # 自定义权限 ]
default_related_name: 设置反向关联关系的默认名称。
class Category(models.Model): name = models.CharField(max_length=100) class Product(models.Model): category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='products') # 自定义反向关联名 # ... class Meta: default_related_name = 'related_products' # 如果没有显式指定 related_name,则默认使用 related_products
Meta 选项的应用场景:
数据库表结构定制: 自定义表名、索引、唯一约束等,优化数据库性能和数据完整性。
Admin 界面友好性: 设置 verbose_name 等选项,提升 Django Admin 界面的可读性和用户体验。
模型行为控制: 通过 ordering, permissions 等选项,控制模型的默认行为和权限管理。
模型验证是确保数据在保存到数据库之前符合预定义规则的过程。Django 提供了多层次的验证机制,包括字段级别的验证和模型级别的验证。
1. 字段级别验证 (Field-level Validation)
字段级别验证是在字段定义时指定的验证规则,例如 max_length, unique, choices, validators 等。当调用模型的 full_clean() 方法或者在表单中使用模型字段时,Django 会自动执行字段级别验证。
from django.db import models from django.core.validators import MinValueValidator class Product(models.Model): name = models.CharField(max_length=100) price = models.DecimalField(max_digits=10, decimal_places=2, validators=[MinValueValidator(0)]) # 价格必须大于等于 0 email = models.EmailField(unique=True) # 邮箱字段唯一
2. 模型级别验证 (Model-level Validation)
模型级别验证是在模型类的 clean() 方法中定义的验证逻辑。模型级别的验证可以跨多个字段进行验证,或者进行更复杂的业务规则验证。
from django.db import models from django.core.exceptions import ValidationError class Event(models.Model): start_time = models.DateTimeField() end_time = models.DateTimeField() def clean(self): super().clean() # 调用父类的 clean 方法 if self.end_time <= self.start_time: raise ValidationError("结束时间必须晚于开始时间。") # 抛出验证错误
在 clean() 方法中,我们可以访问模型的所有字段,并进行跨字段的验证。如果验证不通过,需要抛出 ValidationError 异常,并可以指定错误信息以及关联的字段。
3. full_clean() 方法
full_clean() 方法是触发模型验证的关键方法。它会依次执行以下验证步骤:
调用字段的 clean() 方法进行字段级别验证。
调用模型的 clean() 方法进行模型级别验证。
检查字段的 unique 和 unique_together 约束。
在保存模型实例之前,通常需要调用 full_clean() 方法进行验证,确保数据有效性。例如在视图中处理表单提交的数据时:
from django.shortcuts import render, redirect from .models import Event from .forms import EventForm def create_event(request): if request.method == 'POST': form = EventForm(request.POST) if form.is_valid(): event = form.save(commit=False) # 先不保存到数据库 try: event.full_clean() # 调用 full_clean 进行验证 event.save() # 验证通过后保存 return redirect('event_list') except ValidationError as e: form.add_error(None, e) # 将模型验证错误添加到表单错误中 else: form = EventForm() return render(request, 'create_event.html', {'form': form})
4. 模型验证的应用场景
数据类型和格式验证: 确保数据类型正确,格式符合要求 (例如邮箱、URL、日期格式)。
业务规则验证: 例如,订单金额必须大于 0,用户年龄必须在指定范围内。
数据一致性验证: 跨多个字段验证数据的一致性,例如开始时间必须早于结束时间。
Django 信号允许解耦的应用在特定事件发生时得到通知。模型信号是 Django 信号的一种,它允许我们在模型对象发生特定操作 (例如保存、删除) 时执行自定义的代码。
常用的模型信号:
pre_save: 在模型对象保存之前发送。
post_save: 在模型对象保存之后发送。
pre_delete: 在模型对象删除之前发送。
post_delete: 在模型对象删除之后发送。
m2m_changed: 在模型的多对多关系发生变化时发送。
使用信号的步骤:
定义信号接收器函数 (Signal Receiver): 接收器函数是当信号被触发时执行的函数。接收器函数需要使用 @receiver 装饰器注册到特定的信号。
连接信号和接收器 (Connect Signal and Receiver): 使用 signal.connect(receiver, sender=Model) 将接收器函数连接到特定的信号和模型。sender 参数指定信号发送者,通常是模型类。
from django.db.models.signals import post_save, pre_delete from django.dispatch import receiver from .models import Product @receiver(post_save, sender=Product) # 注册 post_save 信号的接收器,sender 为 Product 模型 def product_post_save_handler(sender, instance, created, **kwargs): if created: print(f"新产品 {instance.name} 已创建!") # 当新产品创建时打印日志 else: print(f"产品 {instance.name} 已更新!") # 当产品更新时打印日志 @receiver(pre_delete, sender=Product) # 注册 pre_delete 信号的接收器,sender 为 Product 模型 def product_pre_delete_handler(sender, instance, **kwargs): print(f"产品 {instance.name} 即将被删除!") # 在产品删除前打印日志
在上述例子中,我们定义了两个信号接收器函数 product_post_save_handler 和 product_pre_delete_handler,分别用于处理 Product 模型的 post_save 和 pre_delete 信号。当 Product 对象被保存或删除时,对应的接收器函数会被自动调用。
信号的应用场景:
日志记录 (Logging): 记录模型对象的创建、更新、删除等操作日志。
缓存更新 (Cache Invalidation): 在模型对象更新时,清除相关缓存。
任务队列 (Task Queues): 在模型对象创建或更新时,触发异步任务 (例如发送邮件、更新索引)。
关联对象更新 (Related Object Updates): 在一个模型对象更新时,自动更新关联模型对象的状态。
权限控制 (Permission Control): 在模型对象保存或删除时,进行权限检查和控制。
信号的优势:
解耦性 (Decoupling): 信号发送者和接收者之间解耦,发送者不需要知道有哪些接收者,接收者只需要关注自己感兴趣的信号。
事件驱动 (Event-driven): 基于事件触发机制,代码逻辑更加清晰和模块化。
可扩展性 (Extensibility): 方便扩展系统功能,添加新的监听器即可,无需修改原有代码。