post_delete and determining if a related object has been deleted

112 views
Skip to first unread message

Stefan Schindler

unread,
Jul 28, 2015, 6:04:20 AM7/28/15
to django...@googlegroups.com
Hey guys,

the post_delete signal is fired before the PK field of the deleted and
all related objects is set to None. This leads to the situation that
one can't determine if a related object has been deleted in a
post_delete handler.

Here's an example:

"Order", "Item" and "LogEntry" are the models. Item has a foreign key
to Order, and LogEntry has a foreign key to Order.

When an Item object is deleted, a new LogEntry object shall be created
and attached to the Item's related Order object.

If the Order object itself is deleted, all post_delete handlers of all
related objects are fired, including Item. Item however stores a new
object with a relation to the *deleted Order* object, which will
result in a constraint error (at least when using transactions in
PostgreSQL, which I do).

pre_delete is no option for me, as the proper objects really have to
be gone in order for the operations to work.

I can see that the behavior has changed 8 years ago:
https://code.djangoproject.com/ticket/5559

Is there any other option I'm missing?

Many greetings,
Stefan Schindler

Erik Cederstrand

unread,
Jul 28, 2015, 8:12:19 AM7/28/15
to Django Users

> Den 28/07/2015 kl. 09.37 skrev Stefan Schindler <st...@boxbox.org>:
>
> If the Order object itself is deleted, all post_delete handlers of all
> related objects are fired, including Item. Item however stores a new
> object with a relation to the *deleted Order* object, which will
> result in a constraint error (at least when using transactions in
> PostgreSQL, which I do).

This makes no sense to me. You want to delete an Item or Order but then immediately create a new one?

If you want to delete an Order but make sure the Item stays, you should use the "on_delete=SET_NULL" argument of ForeignKey fields: https://docs.djangoproject.com/en/1.8/ref/models/fields/#django.db.models.ForeignKey.on_delete

Erik

Stefan Schindler

unread,
Jul 28, 2015, 8:37:45 AM7/28/15
to django...@googlegroups.com
> This makes no sense to me. You want to delete an Item or Order but
> then immediately create a new one?

My actual goal is this: Whenever an Item object itself is deleted, I
want to create a LogEntry object attached to the item's order. If an
order is deleted however, I don't want to do anything.

In code, I expected it to look something like this:

@receiver(post_delete, sender=Item)
def on_item_post_delete(instance, **kwargs):
if instance.order is not None:
LogEntry(order=instance.order).save()

It's technically impossible (AFAIK) however to determine the case
between "Item alone is deleted" and "Item is cascaded by Order
deletion", at the moment.

This also has to do with deferred constraint checking with the
PostgreSQL backend. If contraints were checked immediately, then the
insert would probably fail instantly, instead of the failing
transaction, which I can't handle myself anymore.

I'm really out of ideas.

Stefan

Erik Cederstrand

unread,
Jul 28, 2015, 10:22:42 AM7/28/15
to Django Users

> Den 28/07/2015 kl. 14.36 skrev Stefan Schindler <st...@boxbox.org>:
>
>> This makes no sense to me. You want to delete an Item or Order but
>> then immediately create a new one?
>
> My actual goal is this: Whenever an Item object itself is deleted, I
> want to create a LogEntry object attached to the item's order. If an
> order is deleted however, I don't want to do anything.
>
> In code, I expected it to look something like this:
>
> @receiver(post_delete, sender=Item)
> def on_item_post_delete(instance, **kwargs):
> if instance.order is not None:
> LogEntry(order=instance.order).save()
>
> It's technically impossible (AFAIK) however to determine the case
> between "Item alone is deleted" and "Item is cascaded by Order
> deletion", at the moment.

Signals are nice, but sometimes they just make code more complicated. You could go for this (naive) approach:


class Order(models.Model):
def delete(self, *args, **kwargs):
# Detatch this order from its' items
for item in self.items.all():
item.order = None
item.save()
# Or is this what you really wanted?
# item.delete()
super().delete(*args, **kwargs)


class Item(models.Model):
def delete(self, *args, **kwargs):
if self.order:
LogEntry(order=instance.order).save()
super().delete(*args, **kwargs)


Erik
Reply all
Reply to author
Forward
0 new messages