Cerin
Cerin

Reputation: 64729

How to find all Django foreign key references to an instance

How do you find all direct foreign key references to a specific Django model instance?

I want to delete a record, but I want to maintain all child records that refer to it, so I'm trying to "swap out" the reference to the old record with a different one before I delete it.

This similar question references the Collector class. I tried:

obj_to_delete = MyModel.objects.get(id=blah)
new_obj = MyModel.objects.get(id=blah2)
collector = Collector(using='default')
collector.collect([obj_to_delete])
for other_model, other_data in collector.field_updates.iteritems():
    for (other_field, _value), other_instances in other_data.iteritems():

        # Why is this necessary?
        if other_field.rel.to is not type(first_obj):
            continue

        for other_instance in other_instances:
            setattr(other_instance, other_field.name, new_obj)
            other_instance.save()

# All FK references should be gone, so this should be safe to delete.
obj_to_delete.delete()

However, this seems to have two problems:

  1. Sometimes collector.field_updates contains references to models and fields that have nothing to do with my target obj_to_delete.
  2. My final obj_to_delete.delete() call fails with IntegrityErrors complaining about remaining records that still refer to it, records that weren't caught by the collector.

What am I doing wrong?

I just need a way to lookup all FK references to a single model instance. I don't need any kind of fancy dependency lookup like what's used in Django's standard deletion view.

Upvotes: 3

Views: 3654

Answers (2)

Pynchia
Pynchia

Reputation: 11590

Personally, I think the best option is to avoid the cascaded deletion.

Declaring the foreign keys in the related models with the proper Django option, e.g.

on_delete=models.SET_NULL

should suffice.

Borrowing the sample models from @Joseph's answer:

class Foo(models.Model):
   name = models.CharField(max_length=10)

class Bar(models.Model):
   descr = models.CharField(max_length=100)
   foo = models.ForeignKey(Foo, blank=True, null=True, on_delete=models.SET_NULL))

As described in the official Django docs, here are the predefined behaviours you can use and experiment with:

  • SET_NULL: Set the ForeignKey null; this is only possible if null is True.

  • SET_DEFAULT: Set the ForeignKey to its default value; a default for the ForeignKey must be set.

  • SET(): Set the ForeignKey to the value passed to SET(), or if a callable is passed in, the result of calling it. In most cases, passing a callable will be necessary to avoid executing queries at the time your models.py is imported:

from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models

def get_sentinel_user():
    return get_user_model().objects.get_or_create(username='deleted')[0]

class MyModel(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL,
                             on_delete=models.SET(get_sentinel_user))
  • DO_NOTHING: Take no action. If your database backend enforces referential integrity, this will cause an IntegrityError unless you manually add an SQL ON DELETE constraint to the database field

Upvotes: 2

Joseph
Joseph

Reputation: 13178

You can use Django's reverse foreign key support.

Say you have two models, like so:

class Foo(models.Model):
   name = models.CharField(max_length=10)

class Bar(models.Model):
   descr = models.CharField(max_length=100)
   foo = models.ForeignKey(Foo)

Then you know you can do bar_instance.foo to access the Foo object it keys to. But you can use the reverse foreign key on a Foo instance to get all the Bar objects that point to it using, e.g, foo.bar_set.

Upvotes: 4

Related Questions