25、Django 模型管理器创建与测试实践

Django 模型管理器创建与测试实践

1. 创建模型管理器的必要性

为了提高应用程序的可读性,避免业务逻辑使端点变得杂乱,我们需要为模型类创建管理器。管理器是为 Django 模型提供查询操作的接口。默认情况下,Django 会为每个模型添加一个名为 objects 的管理器。虽然默认管理器有时已足够,但将所有与数据库相关的代码放在模型中是一个很好的实践,这能使代码更一致、易读,且便于测试和维护。

2. 创建自定义异常类

在创建 Order 模型的自定义管理器之前,我们需要创建一些辅助类,首先是自定义异常类。以下是我们要创建的异常类:
- InvalidArgumentError :当向管理器中定义的函数传递无效参数时抛出。

class InvalidArgumentError(Exception):
    def __init__(self, argument_name):
        message = f'The argument {argument_name} is invalid'
        super().__init__(message)
  • OrderAlreadyCompletedError :当尝试将已完成的订单状态再次设置为完成时抛出。
class OrderAlreadyCompletedError(Exception):
    def __init__(self, order):
        message = f'The order with ID: {order.id} is already completed.'
        super().__init__(message)
  • OrderAlreadyCancelledError :当尝试取消已取消的订单时抛出。
class OrderAlreadyCancelledError(Exception):
    def __init__(self, order):
        message = f'The order with ID: {order.id} is already cancelled.'
        super().__init__(message)
  • OrderCancellationError OrderNotFoundError :这两个异常类仅继承自 Exception ,用于特定场景下抛出。
class OrderCancellationError(Exception):
    pass

class OrderNotFoundError(Exception):
    pass
3. 创建状态枚举类

为了避免在代码中使用魔法数字,我们创建一个枚举类来表示订单的不同状态。

from enum import Enum, auto

class Status(Enum):
    Received = auto()
    Processing = auto()
    Payment_Complete = auto()
    Shipping = auto()
    Completed = auto()
    Cancelled = auto()

这样,我们可以使用 Status 枚举来过滤订单,例如: Order.objects.filter(status=Status.Completed.value)

4. 创建 OrderManager

managers.py 文件中创建 OrderManager 类,并定义以下方法:

4.1 set_status 方法

用于设置订单的状态。

class OrderManager(Manager):
    def set_status(self, order, status):
        if status is None or not isinstance(status, Status):
            raise InvalidArgumentError('status')
        if order is None or not isinstance(order, models.Order):
            raise InvalidArgumentError('order')
        if order.status is Status.Completed.value:
            raise OrderAlreadyCompletedError()
        order.status = status.value
        order.save()
4.2 cancel_order 方法

用于取消订单,仅允许取消状态为 Received 的订单。

def cancel_order(self, order):
    if order is None or not isinstance(order, models.Order):
        raise InvalidArgumentError('order')
    if order.status != Status.Received.value:
        raise OrderCancellationError()
    self.set_status(order, Status.Cancelled)
4.3 get_all_orders_by_customer 方法

根据客户 ID 获取所有订单,并按状态和创建时间排序。

def get_all_orders_by_customer(self, customer_id):
    try:
        return self.filter(
            order_customer_id=customer_id).order_by(
            'status', '-created_at')
    except ValueError:
        raise InvalidArgumentError('customer_id')
4.4 get_customer_incomplete_orders get_customer_completed_orders 方法

分别用于获取特定客户的未完成订单和已完成订单。

def get_customer_incomplete_orders(self, customer_id):
    try:
        return self.filter(
            ~Q(status=Status.Completed.value),
            order_customer_id=customer_id).order_by('status')
    except ValueError:
        raise InvalidArgumentError('customer_id')

def get_customer_completed_orders(self, customer_id):
    try:
        return self.filter(
            status=Status.Completed.value,
            order_customer_id=customer_id)
    except ValueError:
        raise InvalidArgumentError('customer_id')
4.5 get_orders_by_status 方法

根据订单状态获取订单。

def get_orders_by_status(self, status):
    if status is None or not isinstance(status, Status):
        raise InvalidArgumentError('status')
    return self.filter(status=status.value)
4.6 get_orders_by_period 方法

根据日期范围获取订单。

def get_orders_by_period(self, start_date, end_date):
    if start_date is None or not isinstance(start_date, datetime):
        raise InvalidArgumentError('start_date')
    if end_date is None or not isinstance(end_date, datetime):
        raise InvalidArgumentError('end_date')
    result = self.filter(created_at__range=[start_date, end_date])
    return result
4.7 set_next_status 方法

将订单状态设置为下一个状态。

def set_next_status(self, order):
    if order is None or not isinstance(order, models.Order):
        raise InvalidArgumentError('order')
    if order.status is Status.Completed.value:
        raise OrderAlreadyCompletedError()
    order.status += 1
    order.save()
5. 使用自定义管理器

model.py 文件的 Order 类末尾添加以下代码,使 Order 模型使用我们创建的 OrderManager

objects = OrderManager()

这样,我们可以通过 Order.objects 访问 OrderManager 中定义的所有方法。

6. 测试的重要性

测试可以让我们知道方法或函数是否按预期工作,并且在对代码进行更改时给我们更多信心。Django 提供了强大的单元和集成测试工具,结合 Selenium 等框架,几乎可以测试整个应用程序。

7. 创建测试文件

在 Django 创建的 test.py 文件中编写测试代码。首先添加必要的导入:

from dateutil.relativedelta import relativedelta
from django.test import TestCase
from django.utils import timezone
from .models import OrderCustomer, Order
from .status import Status
from .exceptions import OrderAlreadyCompletedError
from .exceptions import OrderCancellationError
from .exceptions import InvalidArgumentError
8. 设置测试数据

创建 OrderModelTestCase 类,并定义 setUpTestData 方法来设置测试数据。

class OrderModelTestCase(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.customer_001 = OrderCustomer.objects.create(
            customer_id=1,
            email='customer_001@test.com'
        )
        Order.objects.create(order_customer=cls.customer_001)
        Order.objects.create(order_customer=cls.customer_001,
                             status=Status.Completed.value)
        cls.customer_002 = OrderCustomer.objects.create(
            customer_id=1,
            email='customer_002@test.com'
        )
        Order.objects.create(order_customer=cls.customer_002)
9. 测试 cancel_order 方法

我们将对 cancel_order 方法进行以下测试:
- 测试是否可以取消订单并将其状态设置为 Cancelled
- 测试是否不能取消未收到的订单。
- 测试传递无效参数时是否抛出正确的异常。

def test_cancel_order(self):
    order = Order.objects.get(pk=1)
    self.assertIsNotNone(order)
    self.assertEqual(Status.Received.value, order.status)
    Order.objects.cancel_order(order)
    self.assertEqual(Status.Cancelled.value, order.status)

def test_cancel_completed_order(self):
    order = Order.objects.get(pk=2)
    self.assertIsNotNone(order)
    self.assertEqual(Status.Completed.value, order.status)
    with self.assertRaises(OrderCancellationError):
        Order.objects.cancel_order(order)

def test_cancel_order_with_invalid_argument(self):
    with self.assertRaises(InvalidArgumentError):
        Order.objects.cancel_order({'id': 1})

以下是创建自定义管理器和测试的流程:

graph TD;
    A[创建自定义异常类] --> B[创建状态枚举类];
    B --> C[创建OrderManager类];
    C --> D[在Order模型中使用OrderManager];
    D --> E[创建测试文件];
    E --> F[设置测试数据];
    F --> G[测试cancel_order方法];
步骤 操作
1 创建自定义异常类
2 创建状态枚举类
3 创建 OrderManager
4 Order 模型中使用 OrderManager
5 创建测试文件
6 设置测试数据
7 测试 cancel_order 方法

Django 模型管理器创建与测试实践

10. 测试 set_status 方法

接下来,我们对 set_status 方法进行测试,确保它能正确设置订单状态,并且在异常情况下抛出相应的错误。

def test_set_status_valid(self):
    order = Order.objects.get(pk=1)
    self.assertIsNotNone(order)
    self.assertEqual(Status.Received.value, order.status)
    Order.objects.set_status(order, Status.Processing)
    self.assertEqual(Status.Processing.value, order.status)

def test_set_status_completed_order(self):
    order = Order.objects.get(pk=2)
    self.assertIsNotNone(order)
    self.assertEqual(Status.Completed.value, order.status)
    with self.assertRaises(OrderAlreadyCompletedError):
        Order.objects.set_status(order, Status.Shipping)

def test_set_status_invalid_status(self):
    order = Order.objects.get(pk=1)
    with self.assertRaises(InvalidArgumentError):
        Order.objects.set_status(order, None)
  • test_set_status_valid :测试正常情况下,能否将订单状态设置为指定状态。
  • test_set_status_completed_order :测试当订单已经是完成状态时,尝试更改状态是否会抛出 OrderAlreadyCompletedError
  • test_set_status_invalid_status :测试传递无效的状态参数时,是否会抛出 InvalidArgumentError
11. 测试 get_all_orders_by_customer 方法

get_all_orders_by_customer 方法进行测试,验证其能否根据客户 ID 正确获取订单列表,以及在传递无效客户 ID 时是否抛出异常。

def test_get_all_orders_by_customer_valid(self):
    customer_id = 1
    orders = Order.objects.get_all_orders_by_customer(customer_id)
    self.assertEqual(len(orders), 2)

def test_get_all_orders_by_customer_invalid_id(self):
    with self.assertRaises(InvalidArgumentError):
        Order.objects.get_all_orders_by_customer('invalid_id')
  • test_get_all_orders_by_customer_valid :测试传递有效客户 ID 时,能否正确获取该客户的订单列表。
  • test_get_all_orders_by_customer_invalid_id :测试传递无效客户 ID 时,是否会抛出 InvalidArgumentError
12. 测试 get_customer_incomplete_orders get_customer_completed_orders 方法

分别对获取客户未完成订单和已完成订单的方法进行测试。

def test_get_customer_incomplete_orders(self):
    customer_id = 1
    incomplete_orders = Order.objects.get_customer_incomplete_orders(customer_id)
    self.assertEqual(len(incomplete_orders), 1)

def test_get_customer_completed_orders(self):
    customer_id = 1
    completed_orders = Order.objects.get_customer_completed_orders(customer_id)
    self.assertEqual(len(completed_orders), 1)
  • test_get_customer_incomplete_orders :测试能否正确获取指定客户的未完成订单列表。
  • test_get_customer_completed_orders :测试能否正确获取指定客户的已完成订单列表。
13. 测试 get_orders_by_status 方法

对根据订单状态获取订单列表的方法进行测试。

def test_get_orders_by_status_valid(self):
    orders = Order.objects.get_orders_by_status(Status.Received)
    self.assertEqual(len(orders), 2)

def test_get_orders_by_status_invalid_status(self):
    with self.assertRaises(InvalidArgumentError):
        Order.objects.get_orders_by_status(None)
  • test_get_orders_by_status_valid :测试传递有效状态时,能否正确获取该状态的订单列表。
  • test_get_orders_by_status_invalid_status :测试传递无效状态时,是否会抛出 InvalidArgumentError
14. 测试 get_orders_by_period 方法

对根据日期范围获取订单列表的方法进行测试。

import datetime

def test_get_orders_by_period_valid(self):
    start_date = timezone.now() - relativedelta(days=1)
    end_date = timezone.now() + relativedelta(days=1)
    orders = Order.objects.get_orders_by_period(start_date, end_date)
    self.assertEqual(len(orders), 3)

def test_get_orders_by_period_invalid_start_date(self):
    end_date = timezone.now() + relativedelta(days=1)
    with self.assertRaises(InvalidArgumentError):
        Order.objects.get_orders_by_period(None, end_date)
  • test_get_orders_by_period_valid :测试传递有效日期范围时,能否正确获取该日期范围内的订单列表。
  • test_get_orders_by_period_invalid_start_date :测试传递无效的开始日期时,是否会抛出 InvalidArgumentError
15. 测试 set_next_status 方法

对将订单状态设置为下一个状态的方法进行测试。

def test_set_next_status_valid(self):
    order = Order.objects.get(pk=1)
    self.assertIsNotNone(order)
    self.assertEqual(Status.Received.value, order.status)
    Order.objects.set_next_status(order)
    self.assertEqual(Status.Processing.value, order.status)

def test_set_next_status_completed_order(self):
    order = Order.objects.get(pk=2)
    self.assertIsNotNone(order)
    self.assertEqual(Status.Completed.value, order.status)
    with self.assertRaises(OrderAlreadyCompletedError):
        Order.objects.set_next_status(order)
  • test_set_next_status_valid :测试正常情况下,能否将订单状态设置为下一个状态。
  • test_set_next_status_completed_order :测试当订单已经是完成状态时,尝试设置下一个状态是否会抛出 OrderAlreadyCompletedError
16. 测试流程总结

以下是整个测试流程的 mermaid 流程图:

graph TD;
    A[测试set_status方法] --> B[测试get_all_orders_by_customer方法];
    B --> C[测试get_customer_incomplete_orders和get_customer_completed_orders方法];
    C --> D[测试get_orders_by_status方法];
    D --> E[测试get_orders_by_period方法];
    E --> F[测试set_next_status方法];
步骤 测试方法 测试目的
1 test_set_status 验证 set_status 方法的正常使用和异常处理
2 test_get_all_orders_by_customer 验证根据客户 ID 获取订单列表的功能和异常处理
3 test_get_customer_incomplete_orders test_get_customer_completed_orders 验证获取客户未完成和已完成订单列表的功能
4 test_get_orders_by_status 验证根据订单状态获取订单列表的功能和异常处理
5 test_get_orders_by_period 验证根据日期范围获取订单列表的功能和异常处理
6 test_set_next_status 验证将订单状态设置为下一个状态的功能和异常处理

通过以上一系列的测试,我们可以确保 OrderManager 中的各个方法在不同情况下都能正常工作,并且在出现异常时能正确抛出相应的错误,从而提高代码的健壮性和可维护性。在实际开发中,我们可以根据需求添加更多的测试用例,进一步完善测试覆盖度。

采用PyQt5框架Python编程语言构建图书信息管理平台 本项目基于Python编程环境,结合PyQt5图形界面开发库,设计实现了一套完整的图书信息管理解决方案。该系统主要面向图书馆、书店等机构的日常运营需求,通过模块化设计实现了图书信息的标准化管理流程。 系统架构采用典型的三层设计模式,包含数据存储层、业务逻辑层和用户界面层。数据持久化方案支持SQLite轻量级数据库MySQL企业级数据库的双重配置选项,通过统一的数据库操作接口实现数据存取隔离。在数据建模方面,设计了包含图书基本信息、读者档案、借阅记录等核心数据实体,各实体间通过主外键约束建立关联关系。 核心功能模块包含六大子系统: 1. 图书编目管理:支持国际标准书号、中国图书馆分类法等专业元数据的规范化著录,提供批量导入单条录入两种数据采集方式 2. 库存动态监控:实时追踪在架数量、借出状态、预约队列等流通指标,设置库存预警阈值自动提醒补货 3. 读者服务管理:建立完整的读者信用评价体系,记录借阅历史违规行为,实施差异化借阅权限管理 4. 流通业务处理:涵盖借书登记、归还处理、续借申请、逾期计算等标准业务流程,支持射频识别技术设备集成 5. 统计报表生成:按日/月/年周期自动生成流通统计、热门图书排行、读者活跃度等多维度分析图表 6. 系统维护配置:提供用户权限分级管理、数据备份恢复、操作日志审计等管理功能 在技术实现层面,界面设计遵循Material Design设计规范,采用QSS样式表实现视觉定制化。通过信号槽机制实现前后端数据双向绑定,运用多线程处理技术保障界面响应流畅度。数据验证机制包含前端格式校验后端业务规则双重保障,关键操作均设有二次确认流程。 该系统适用于中小型图书管理场景,通过可扩展的插件架构支持功能模块的灵活组合。开发过程中特别注重代码的可维护性,采用面向对象编程范式实现高内聚低耦合的组件设计,为后续功能迭代奠定技术基础。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值