Reputation: 64729
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:
collector.field_updates
contains references to models and fields that have nothing to do with my target obj_to_delete
. 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
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))
Upvotes: 2
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