我有两个模型,一个是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'))
上面的解决方案没有产生任何结果,它只是触发了我的信号,并像往常一样“粘”在那里。
如何处理此问题?
1条答案
按热度按时间6ie5vjzr1#
你的
OneToOneField
放错地方了。如果我对你的模型理解正确的话,你应该先创建车辆,然后创建相关的资产。OneToOneField
应该更适合Asset
模型。这有多个好处:
1.您不必使用
asset = None
创建Vehicle
,也不必执行另一个查询来将asset
设置为新创建的资产。1.当你删除一个
Vehicle
时,由于你定义了on_delete=CASCADE
,Asset
的删除将由Django自动处理。因此,您不必使用信号来保持
Vehicle
和Asset
之间的完整性。信号对于这类事情是相当不可靠的。例如,您的信号不会在Vehicle.objects.filter(...).delete()
这样的语句上被调用。在您的情况下,我怀疑问题是某种循环依赖,它会导致无限的SQL锁。或者在第一个事务尚未提交时,在另一个事务中发送并执行了该信号。在任何情况下,信号都有几个陷阱,必须是最后的解决方案。只要看看Django文档中的所有警告,就可以找到一个更好的实现:
信号
信号给予人一种松散耦合的感觉,但它们很快就会导致代码难以理解、调整和调试。
在可能的情况下,您应该选择直接调用处理代码,而不是通过信号调度。
模型信号
信号会使代码更难维护。请考虑在自定义管理器上实现Helper方法,以更新模型并执行其他逻辑,或者在使用模型信号之前重写模型方法。
这些信号中有许多是通过各种模型方法(如
__init__()
或save()
)发送的,您可以在自己的代码中覆盖这些方法。如果在模型上重写这些方法,则必须调用父类的方法才能发送这些信号。
注意,Django默认将信号处理程序存储为弱引用,所以如果你的处理程序是一个本地函数,它可能会被垃圾回收。为了防止这种情况,在调用信号的
connect()
时传递weak=False
。如果你真的想在
Vehicle
模型中坚持你对OneToOneField
的定义,我建议你不要使用信号,而是这样做:然而,这只是一个肮脏的变通办法,将您的
OneToOneField
移动到Asset
模型将更加无缝地工作。