Nullable ForeignKeys y eliminación de una instancia de modelo de referencia

Tengo una ForeignKey que puede ser nula en mi modelo para modelar un acoplamiento suelto entre los modelos. Parece algo así:

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

Al guardar, el nombre del remitente se escribe en el atributo sender_name. Ahora, quiero poder eliminar la instancia de Usuario a la que hace referencia el remitente y dejar el mensaje en su lugar.

Fuera de la caja, este código siempre genera mensajes eliminados tan pronto como elimino la instancia de Usuario. Así que pensé que un manejador de señales sería una buena idea.

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

Lamentablemente, de ninguna manera es una solución. De alguna manera, Django primero recostack lo que quiere eliminar y luego dispara el controlador pre_delete.

¿Algunas ideas? ¿Dónde está el nudo en mi cerebro?

Django sí emula el comportamiento de ON DELETE CASCADE SQL, y no hay una forma documentada para cambiar esto. Los documentos en los que mencionan esto se encuentran cerca del final de esta sección: Eliminación de objetos .

Tiene razón en que Django’s recostack todas las instancias de modelos relacionadas, y luego llama al controlador de eliminación previa para cada una. El remitente de la señal será la clase de modelo que se va a eliminar, en este caso Message , en lugar de User , lo que dificulta la detección de la diferencia entre una eliminación en cascada activada por el Usuario y una eliminación normal … especialmente debido a la señal para eliminar la clase de usuario es la última, ya que es la última eliminación 🙂

Sin embargo, puede obtener la lista de objetos que Django propone eliminar antes de llamar a la función User.delete (). Cada instancia de modelo tiene un método _collect_sub_objects() llamado _collect_sub_objects() que comstack la lista de instancias con claves externas que lo señalan (comstack esta lista sin eliminar las instancias). Puede ver cómo se llama a este método mirando delete() en django.db.base .

Si este era uno de sus propios objetos, recomendaría anular el método delete() en su instancia para ejecutar _collect_sub_objects (), y luego romper las ForeignKeys antes de llamar a la superclase delete. Ya que está usando un objeto Django incorporado que puede ser muy difícil de subclasificar (aunque es posible sustituir su propio objeto Usuario por django), es posible que tenga que confiar en la lógica de visualización para ejecutar _collect_sub_objects y romper los FKs antes supresión.

Aquí hay un ejemplo rápido y sucio:

 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 this i.save() u.delete() 

Acabo de descubrir el comportamiento de ON DELETE CASCADE, veo que en Django 1.3 han hecho que el comportamiento de la clave externa sea configurable .