Django pass self to models.SET on_delete

天大地大妈咪最大 提交于 2019-12-08 08:01:55

问题


I want to modify a foreign key value when its deleted from the database. So I looked upon the doc and used on_delete=models.SET(foo) method. https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.SET

This is my model definition

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)

And this is my method which is called on delete

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

However the problem here is that I can't pass argument to this method which is why I cannot know which product was deleted. I also tried using signals as specified in this answer django model on_delete pass self to models.SET()

I also tried using signals, but that also didn't work. I can't seem to find out a proper solution for this. Let me know if someone has an idea on how to achieve this.

EDIT

This is the code I am using in signals

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

Now the problem is that django tries to delete my orderlines as well (as default on_delete is set to cascade). And if I set the on_Delete to SET_NULL, it sets the foreign key to null.

EDIT -2 Here is the code I am using

@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 Posting full implementation for completeness.

@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()            

回答1:


Signals are the right way to do this.

You can get the OrderLine from the signal receiver:

@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 is a queryset which you can iterate over or update in bulk.

EDIT

Turns out the approach suggested above will not work, because by the time the pre_delete signal has fired Django has already determined which related models it needs to process with on_delete, and will overwrite these changes.

This approach will work, though it is a bit clunky:

First, in a pre_delete receiver: from copy import copy

@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())

Then, in a post_delete receiver:

@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:
        # ... 

In between these two events, Django will have performed SET_NULL (or whatever you configured) on the OrderLines.




回答2:


Giving support to the @solarissmoke answer, which I consider the right solution, but to give you more option I will post this. If you have the product instance you can have its orderlines. For example if you add a related_name to the OrderLine.product like so:

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

you can then in your signal do this:

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

Just another way of doing that, to get the orderlines.



来源:https://stackoverflow.com/questions/36571834/django-pass-self-to-models-set-on-delete

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!