Django REST framework中覆盖模型DELETE方法实现软删并保留外键数据
问题1:软删除方案在DRF中的可行性及实现方法
该方案完全可行,且是Django生态中非常成熟的实现方案,具体实现步骤如下:
步骤1:修改Product模型,新增软删除相关字段和自定义管理器
重写模型自带的delete方法,默认将删除操作改为标记删除状态,同时自定义查询管理器,正常查询时自动过滤已删除的商品,避免已停售商品出现在前台列表、下单等接口中。
from django.db import models from django.utils import timezone # 自定义软删除查询管理器 class SoftDeleteManager(models.Manager): def get_queryset(self): return super().get_queryset().filter(is_deleted=False) class Product(models.Model): # 原有字段保持不变 name = models.CharField(max_length=100) price = models.DecimalField(max_digits=10, decimal_places=2) # 新增软删除字段 is_deleted = models.BooleanField(default=False, verbose_name="是否删除") deleted_at = models.DateTimeField(null=True, blank=True, verbose_name="删除时间") # 默认查询管理器自动过滤已删除商品 objects = SoftDeleteManager() # 额外提供全量查询管理器,用于订单关联、审计等场景查询已删除商品 all_objects = models.Manager() def delete(self, *args, **kwargs): # 默认执行软删除 self.is_deleted = True self.deleted_at = timezone.now() self.save() # 可选:传参支持硬删除 if kwargs.pop('hard_delete', False): return super().delete(*args, **kwargs)
步骤2:DRF视图层适配
如果使用ModelViewSet,不需要额外修改销毁逻辑,调用instance.delete()时会自动执行软删除逻辑,也可以显式重写destroy方法做自定义控制:
from rest_framework import viewsets, status from rest_framework.response import Response from .models import Product from .serializers import ProductSerializer class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all() serializer_class = ProductSerializer def destroy(self, request, *args, **kwargs): instance = self.get_object() instance.delete() return Response(status=status.HTTP_204_NO_CONTENT)
该方案不会触发原有外键的on_delete=SET_NULL规则,因为未真实删除Product表的记录,Order中关联的外键值完全保留,满足订单记录不丢失的需求。如果需要查询订单对应的商品信息,可通过Product.all_objects.get(id=order.product_id)获取包括已删除在内的全量商品数据。
问题2:其他实现需求的最佳实践
除软删除外,还有以下成熟方案可按需选择:
- 商品快照冗余存储:这是电商订单场景的首选方案,直接在Order模型中冗余存储下单时点的商品核心信息,不受后续商品修改、删除的影响,完全符合审计日志的准确性要求。
代码示例:
下单时直接将当前Product的对应信息写入冗余字段即可,后续即使硬删除Product,订单的核心信息也完全保留。class Order(models.Model): product = models.ForeignKey(Product, blank=True, null=True, on_delete=models.SET_NULL) # 冗余存储商品快照,可根据业务需求存JSON或者单独拆分为字段 product_name = models.CharField(max_length=100, blank=True, verbose_name="下单时商品名称") product_price = models.DecimalField(max_digits=10, decimal_places=2, blank=True, verbose_name="下单时商品价格") product_snapshot = models.JSONField(default=dict, verbose_name="完整商品快照") - 外键规则改为
SET_DEFAULT:提前在Product表中创建一条固定的「已下架商品」默认记录,将Order外键的on_delete规则改为models.SET_DEFAULT,默认指向这条记录,删除商品时关联订单的外键会自动指向该默认记录,不会被置空。该方案适合不需要保留原商品具体信息的轻量化场景。 - 归档下架商品:单独创建
ArchivedProduct归档表,删除商品时将原商品数据迁移到归档表中,Order模型新增归档商品外键字段,删除时自动关联到归档记录,适合商品数据量极大、需要降低主表查询压力的场景。
内容的提问来源于stack exchange,提问作者R. Liu
相关产品推荐
相关产品推荐

