Django 将 self 传递给 models.SET on_delete

Posted

技术标签:

【中文标题】Django 将 self 传递给 models.SET on_delete【英文标题】:Django pass self to models.SET on_delete 【发布时间】:2016-08-02 23:32:41 【问题描述】:

我想在从数据库中删除外键值时对其进行修改。所以我查看了文档并使用了 on_delete=models.SET(foo) 方法。 https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.SET

这是我的模型定义

class OrderLine(models.Model):
    product = models.ForeignKey(Product, on_delete=models.SET(getDuplicateProduct), null=True)
    quantity = models.PositiveSmallIntegerField(default=1)
    finalPricePerUnit = models.PositiveIntegerField()
    order = models.ForeignKey(Order, on_delete=models.PROTECT)
    dateCreated = models.DateTimeField(auto_now=False, auto_now_add=True)

这是我在删除时调用的方法

def getDuplicateProduct(orderline):
    productToDelete = orderline.product
    # some logic to generate duplicate copy and returning it

但是这里的问题是我不能将参数传递给这个方法,这就是为什么我不知道哪个产品被删除了。我还尝试使用此答案中指定的信号django model on_delete pass self to models.SET()

我也尝试过使用信号,但这也没有用。我似乎无法为此找到合适的解决方案。 如果有人知道如何实现这一点,请告诉我。

编辑

这是我在信号中使用的代码

@receiver(pre_delete, sender=Product)
def getDuplicateProduct(sender, **kwargs):
    product = kwargs['instance']
    orderlines = product.orderline_set.all()
    #further processing

现在的问题是 django 也试图删除我的订单(因为默认的 on_delete 设置为级联)。如果我将 on_Delete 设置为 SET_NULL,它会将外键设置为 null。

编辑 -2 这是我正在使用的代码

@receiver(pre_delete, sender=Product)
def getDuplicateProduct(sender, **kwargs):
    product = kwargs['instance']
    orderlines = product.orderline_set.all()
    product.name = product.name + ' ' + product.get_type_display()
    newProduct = deepcopy(product)
    newProduct.name = product.name + ' ' + product.get_type_display()
    newProduct.pk=None
    newProduct.id=None
    newProduct.save()
    product.duplicateProductId = newProduct.id
    product.old_orderlines = orderlines
    product.save()


@receiver(post_delete, sender=Product)
def handlePostDelete(sender, **kwargs):
    product = kwargs['instance']
    newProduct = Product.objects.get(id=product.duplicateProductId)
    for orderline in product.old_orderlines:
        orderline.product = newProduct
        orderline.save()

EDIT-3 发布完整实施以确保完整性。

@receiver(pre_delete, sender=Product)
def handlePreDelete(sender, **kwargs):
    product = kwargs['instance']
    orderlines = product.orderline_set.all()
    shouldCreate=False
    for orderline in orderlines:
        if orderline.order.status>1:
            shouldCreate=True
    product.shouldCreate = shouldCreate
    if shouldCreate:
        product.old_orderlines = orderlines
        product.save()
    else:
        product.save()
        return None


@receiver(post_delete, sender=Product)
def handlePostDelete(sender, **kwargs):
    product = kwargs['instance']
    shouldCreate = product.shouldCreate
    if shouldCreate:
        newProduct = deepcopy(product)
        newProduct.name = product.name + ' ' + product.get_type_display()
        newProduct.pk=None
        newProduct.id=None
        newProduct.save()
        # Do whatever you want with product.old_orderlines
        for orderline in product.old_orderlines:
            orderline.product = newProduct
            orderline.save()            

【问题讨论】:

"我不会在receiver方法中引用任何orderline。"。为什么不?你能发布你使用信号的代码吗? 在您的第二次编辑中,我认为是 deepcopy 导致了问题。看看你是否可以把它移到post_delete 处理程序中。您还需要复制查询集以避免它被破坏(我在下面编辑了我的答案)。 @solarissmoke 我尝试将深层副本移动到 post_delete 处理程序,它似乎工作,感谢和抱歉迟到的回复,出城 10 天。 【参考方案1】:

信号是做到这一点的正确方法。

您可以从信号接收器获取OrderLine

@receiver(pre_delete, sender=Product)
def getDuplicateProduct(sender, **kwargs):
    product = kwargs['instance']
    orderlines = product.orderline_set.all()
    # orderlines contains all the OrderLines foreign keyed to the product.

orderlines 是一个查询集,您可以对其进行迭代或update in bulk。

编辑

原来上面建议的方法行不通,因为当pre_delete 信号被触发时,Django 已经确定了它需要用on_delete 处理哪些相关模型,并将覆盖这些更改。

这种方法可行,虽然有点笨拙:

首先,在pre_delete 接收器中: 从副本导入副本

@receiver(pre_delete, sender=Product)
def handlePreDelete(sender, **kwargs):
    product = kwargs['instance']
    # Store the OrderLines as a property of the object
    # Have to copy it otherwise it will be empty later
    product.old_orderlines = copy(product.orderline_set.all())

然后,在post_delete 接收器中:

@receiver(post_delete, sender=Product)
def handlePostDelete(sender, **kwargs):
    product = kwargs['instance']
    # Do whatever you want with product.old_orderlines
    for line in product.old_orderlines:
        # ... 

在这两个事件之间,Django 将在 OrderLines 上执行SET_NULL(或您配置的任何内容)。

【讨论】:

这个解决方案的问题是 django 试图删除我的订单以及默认的 on_delete 设置为级联。如果我将 on_Delete 设置为 SET_NULL,它会将外键设置为 null 我认为想法是在删除发生之前将 ForeignKey 更改为指向另一个产品? 没错,我想将订单线指向一个新产品。但是我不确定此更改是否正确提交或存在竞争条件。 嗯,你是对的。在触发此信号时,Django 已经确定需要删除/设置为 null 的内容,因此这种方法不起作用。让我用另一种方法编辑答案。 我已经用一种应该可行的方法编辑了答案。【参考方案2】:

支持@solarissmoke 答案,我认为这是正确的解决方案,但为了给你更多选择,我会发布这个。如果你有product 实例,你可以拥有它的orderlines。例如,如果您将related_name 添加到OrderLine.product,如下所示:

class OrderLine(models.Model):
    product = models.ForeignKey(Product, related_name='orderlines')

然后你可以在你的信号中这样做:

@receiver(pre_delete, sender=Product)
def getDuplicateProduct(sender, **kwargs):
    product = kwargs['instance']
    orderlines = product.orderlines.all()

获得orderlines的另一种方法。

【讨论】:

以上是关于Django 将 self 传递给 models.SET on_delete的主要内容,如果未能解决你的问题,请参考以下文章

django - 将参数从模板传递到 models.py 的函数

Django REST:Auth用户未传递给序列化程序错误 - 需要字段

如何将额外的 URL 值传递给 Django 通用 DeleteView?

在 Django 中,如何将 ForeignKey 传递给 Model 的实例?

将kwargs传递给django管理表单

如何在 django 的基于类的视图中将值传递给模板?