django -删除一个示例时如何处理完整性错误异常?

4uqofj5v  于 2022-11-18  发布在  Go
关注(0)|答案(1)|浏览(148)

我有两个模型,一个是Vehicle,另一个是Asset,它们之间是一对一的关系。
车型:

class Vehicle(commModels.Base):
   
    vehicle_no = models.CharField(max_length=16, unique=True)
    vehicle_type = models.ForeignKey(VehicleType, related_name='%(class)s_vehicle_type', on_delete=models.SET_NULL, default=None, null=True)
    tonage = models.DecimalField(max_digits=4, decimal_places=1, blank=True, default=None, null=True)
    asset = models.OneToOneField(
                        assetModels.Asset,
                        related_name='vehicle',
                        on_delete=models.CASCADE,
                        null=True,
                    )
.....

资产型号:

reg_no = models.CharField(max_length=100, unique=True)
    name = models.CharField(max_length=255, null=True, blank=True)
    type = models.ForeignKey(AssetType, related_name='%(class)s_type', on_delete=models.SET_NULL, null=True)
   
    user = models.ForeignKey(
        orgModels.Company,
        related_name='%(class)s_user',
        on_delete=models.CASCADE,
        blank=True,
        null=True,
    )

当我删除车辆时,与车辆相关的资产也应被删除。
我的删除车辆代码:

@login_required
def vehicle_delete_view(request, id=None):
        
    obj = get_object_or_404(Vehicle, id=id)

    if request.method == "DELETE":
        try:    
            
            obj.delete() # this code causing an issue
         
            messages.success(request, 'Vehicle has been deleted!')
        except:
            print('exception triger-------')
            messages.error(request, 'Vehicle cannot be deleted!')
            return HTTPResponseHXRedirect(request.META.get('HTTP_REFERER'))

    return HTTPResponseHXRedirect(reverse_lazy('vehicle_list'))

现在,我创建了一个信号,在成功删除后删除与车辆绑定的资产。
signal.py:

@receiver(post_delete, sender=models.Vehicle)
def delete_asset(sender, instance, *args, **kwargs):
    print('signal is trigger')
    if instance.asset:
        asset = asset_models.Asset.objects.get(id=instance.asset_id)
        if asset: 
            print('asset is being delete')
            asset.delete()

最重要的是,我的车辆在其他几个模型上也有外键约束指向它。当我创建一辆新车时,它也会为资产创建一个示例。所以当我删除该车辆时,它不会触发信号,也会删除它。所以它工作得很好。
然而,我面临的问题是,如果我试图删除一辆被除Asset之外的其他模型引用的车辆,那么它就会成为一个问题。问题是有一个外部约束错误,但我的try catch块无法捕获它。由于它没有成功地从车辆中删除示例,但它仍然触发了信号。当它触发信号时,它只会打印asset is being delete并永远停留在那里。
我很困惑为什么post_delete会在信号中被触发,因为它根本没有删除车辆。即使当我试图从我的数据库中手动删除它时,我也会得到一个错误:

/* ERROR:  canceling statement due to statement timeout
CONTEXT:  while locking tuple (27,15) in relation "vehicle_vehicle" */

我试图找到一种方法,这样如果obj.delete()抛出一个错误(显然它确实抛出了),我就能够捕获并处理它,甚至不需要触发信号,因为车辆没有被删除。因此,它应该能够重定向请求,但相反,在我的页面上没有任何响应,仍然触发信号。
我尝试捕捉特定异常的情况如下:

from django.db import IntegrityError
        try:    
            # delete vehicle that are not referenced by other models
            obj.delete()
         
            messages.success(request, 'Vehicle has been deleted!')
        except IntegrityError as e :
            print('exception triger-------')
            print(e.__cause__)
            messages.error(request, 'Vehicle cannot be deleted!')
            return HTTPResponseHXRedirect(request.META.get('HTTP_REFERER'))

上面的解决方案没有产生任何结果,它只是触发了我的信号,并像往常一样“粘”在那里。
如何处理此问题?

6ie5vjzr

6ie5vjzr1#

你的OneToOneField放错地方了。如果我对你的模型理解正确的话,你应该先创建车辆,然后创建相关的资产。OneToOneField应该更适合Asset模型。

class Asset(models.Model):
    vehicle = models.OneToOneField(
                    Vehicle,
                    related_name='asset',
                    on_delete=models.CASCADE,
                )

这有多个好处:
1.您不必使用asset = None创建Vehicle,也不必执行另一个查询来将asset设置为新创建的资产。
1.当你删除一个Vehicle时,由于你定义了on_delete=CASCADEAsset的删除将由Django自动处理。
因此,您不必使用信号来保持VehicleAsset之间的完整性。信号对于这类事情是相当不可靠的。例如,您的信号不会在Vehicle.objects.filter(...).delete()这样的语句上被调用。在您的情况下,我怀疑问题是某种循环依赖,它会导致无限的SQL锁。或者在第一个事务尚未提交时,在另一个事务中发送并执行了该信号。
在任何情况下,信号都有几个陷阱,必须是最后的解决方案。只要看看Django文档中的所有警告,就可以找到一个更好的实现:

信号

信号给予人一种松散耦合的感觉,但它们很快就会导致代码难以理解、调整和调试。
在可能的情况下,您应该选择直接调用处理代码,而不是通过信号调度。

模型信号

信号会使代码更难维护。请考虑在自定义管理器上实现Helper方法,以更新模型并执行其他逻辑,或者在使用模型信号之前重写模型方法。
这些信号中有许多是通过各种模型方法(如__init__()save())发送的,您可以在自己的代码中覆盖这些方法。
如果在模型上重写这些方法,则必须调用父类的方法才能发送这些信号。
注意,Django默认将信号处理程序存储为弱引用,所以如果你的处理程序是一个本地函数,它可能会被垃圾回收。为了防止这种情况,在调用信号的connect()时传递weak=False
如果你真的想在Vehicle模型中坚持你对OneToOneField的定义,我建议你不要使用信号,而是这样做:

if obj.asset:
    obj.asset.delete()
    # Deleting the asset will automatically delete the Vehicle as you set on_delete=CASCADE
else:
    obj.delete()

然而,这只是一个肮脏的变通办法,将您的OneToOneField移动到Asset模型将更加无缝地工作。

相关问题