takje
takje

Reputation: 2800

Find all objects of a certain class that do not have any active links with other objects

I have a class A which is used as a Foreign Key in many other classes.

class A(models.Model):
  pass

class B(models.Model):
  a: A = ForeignKey(A)

class C(models.Model):
  other_name: A = ForeignKey(A)

Now I have a database with a huge table of A objects and many classes like B and C who reference A (say potentially dozens). In this table, there are many objects (100k+) and I want to clean up all objects that are not actively referenced by other objects with a Foreign Key. For example, object 1 of class A is not referenced by class B and C.

How would I do this? I already came up with the following code:

a_list: list = list()
classes: list[tuple] = [(B, "a"), (C, "other_name")]

for cl, field in classes:
  field_object: Field = cl._meta.get_field(field)
  for obj in cl.objects.all():
    a: A = field_object.value_from_object(obj)
    a_list.append(a)

to_remove: list[A] = [a for a in A.objects.all() if a not in a_list]
for a in to_remove():
  a.remove()

This leaves me with a few questions:

Upvotes: 1

Views: 25

Answers (1)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 477533

You can filter with:

A.objects.filter(b=None, c=None).delete()

This will make proper JOINs and thus determine the items in a single querying, without having to fetch all other model records from the database.

But this will be expensive anyway, since the triggers are done by Django that will thus "collect" all A objects.

If you do not know what is referencing A, you can work with the meta of the model, so:

from django.db.models.fields.reverse_related import OneToOneRel

fields = {
    f.related_query_name: None
    for f in A._meta.get_fields()
    if isinstance(f, ManyToOneRel)
}

A.objects.filter(**fields).delete()

This will look for all ForeignKeys and OneToOneFields from other models that target (directly) the A model, then make LEFT OUTER JOINs and filter on NULL, and then delete those.

I would advise to first inspect A.objects.filter(**fields) however, and make sure you do not remove any items that are still necessary.

Upvotes: 1

Related Questions