Nullable ForeignKeys and deleting a referenced model instance

2024/10/15 1:24:58

I have a ForeignKey which can be null in my model to model a loose coupling between the models. It looks somewhat like that:

class Message(models.Model):sender = models.ForeignKey(User, null=True, blank=True)sender_name = models.CharField(max_length=255)

On save the senders name is written to the sender_name attribute. Now, I want to be able to delete the User instance referenced by the sender and leave the message in place.

Out of the box, this code always results in deleted messages as soon as I delete the User instance. So I thought a signal handler would be a good idea.

def my_signal_handler(sender, instance, **kwargs):instance.message_set.clear()
pre_delete.connect(my_signal_handler, sender=User)

Sadly, it is by no means a solution. Somehow Django first collects what it wants to delete and then fires the pre_delete handler.

Any ideas? Where is the knot in my brain?

Answer

Django does indeed emulate SQL's ON DELETE CASCADE behaviour, and there's no out-of-the box documented way to change this. The docs where they mention this are near the end of this section: Deleting objects.

You are right that Django's collects all related model instances, then calls the pre-delete handler for each. The sender of the signal will be the model class about to be deleted, in this case Message, rather than User, which makes it hard to detect the difference between a cascade delete triggered by User and a normal delete... especially since the signal for deleting the User class comes last, since that's the last deletion :-)

You can, however, get the list of objects that Django is proposing to delete in advance of calling the User.delete() function. Each model instance has a semi-private method called _collect_sub_objects() that compiles the list of instances with foreign keys pointing to it (it compiles this list without deleting the instances). You can see how this method is called by looking at delete() in django.db.base.

If this was one of your own objects, I'd recommend overriding the delete() method on your instance to run _collect_sub_objects(), and then break the ForeignKeys before calling the super class delete. Since you're using a built-in Django object that you may find too difficult to subclass (though it is possible to substitute your own User object for django's), you may have to rely on view logic to run _collect_sub_objects and break the FKs before deletion.

Here's a quick-and-dirty example:

from django.db.models.query import CollectedObjects
u = User.objects.get(id=1)instances_to_be_deleted = CollectedObjects()
u._collect_sub_objects(instances_to_be_deleted)for k in instances_to_be_deleted.ordered_keys():inst_dict = instances_to_be_deleted.data[k]for i in inst_dict.values():i.sender = None  # You will need a more generic way for thisi.save()u.delete()
https://en.xdnf.cn/q/69345.html

Related Q&A

UnrecognizedImageError - image insertion error - python-docx

I am trying to insert an wmf file to docx using python-docx which is producing the following traceback. Traceback (most recent call last):File "C:/Users/ADMIN/PycharmProjects/ppt-to-word/ppt_reade…

Python pool map and choosing number of processes

In setting the number of processes, Id be keen to see how many threads I can actually use on my machine - how do I find this? Is there a way to determine the number of threads available to me?

connection times out when trying to connect to mongodb atlas with python

Im trying to connect to my mongodb atlas cluster but i keep getting timed out as soon as i try to do something with my db. The db i use was created in mongoshell and also the collection i checked their…

Supervisor not working with Gunicorn + Flask

I am trying to run Gunicorn from Supervisor in an Ubuntu 12.04 system. Gunicorn runs a Flask app (simple REST web service tested with Flasks embedded server). I have installed Gunicorn by clonning GIT …

How to hash int/long using hashlib in Python?

Im developing a set of cryptographic algorithms / protocols for educational purposes. Specifically, I am currently working on OAEP encoding.OAEP involves use of cryptographic hash functions; therefore …

SQLAlchemy: Override relationship-defined order_by in a query

So, I have a model that is something like:class Foo(model):__tablename__ = "foo"id = Column(Integer, primary_key=True)data = relationship("FooData",cascade="all, delete-orphan&…

Toplevel in Tkinter: Prevent Two Windows from Opening

Say I have some simple code, like this:from Tkinter import * root = Tk() app = Toplevel(root) app.mainloop()This opens two windows: the Toplevel(root) window and the Tk() window. Is it possible to avoi…

Specify File path in tkinter File dialog

I have a file dialog to open a file, however, the file that I want to open is in a different directory than the program I wrote. The file dialog opens to the directory where I am. Is there a way to s…

Why does scipy linear interpolation run faster than nearest neighbor interpolation?

Ive written a routine that interpolates point data onto a regular grid. However, I find that scipys implementation of nearest neighbor interpolation performs almost twice as slow as the radial basis f…

How do I create a 404 page?

My application catches all url requests with an @app.route, but occasionally I bump into a bad url for which I have no matching jinja file (bu it does match an existing @app.route). So I want to redire…