When working with Django applications, you might need to find all dependent records that reference a particular model instance through foreign key relationships. Django's ORM provides a powerful tool called Collector that can help you identify and collect all related objects before performing operations like deletion.
In this article, we'll explore how to use Django's Collector to get a list of dependent tables linked with foreign keys, given a primary key of a table, and print their primary keys.
Understanding Django ORM Collector
Django's Collector is an internal utility used by the ORM to collect related objects. It's primarily used during cascade deletions, but we can leverage it to discover all foreign key relationships for a given model instance.
Implementation
Here's a function that takes a model class and a primary key, then returns all dependent tables and their primary keys:
from django.db.models.deletion import Collector
from django.db import models
def get_dependent_tables_with_foreign_keys(model_class, primary_key):
"""
Given a model class and primary key, get all dependent tables
linked with foreign keys and their primary keys.
Args:
model_class: Django model class
primary_key: Primary key value of the instance
Returns:
dict: Dictionary with dependent table names as keys and
list of primary keys as values
"""
try:
# Get the instance
instance = model_class.objects.get(pk=primary_key)
except model_class.DoesNotExist:
return {}
# Create a collector instance
collector = Collector(using='default')
# Add the instance to collector
collector.collect([instance])
# Dictionary to store results
dependent_tables = {}
# Iterate through collected objects
for model, instances in collector.data.items():
# Skip the original model
if model == model_class:
continue
# Get primary keys of dependent instances
primary_keys = [obj.pk for obj in instances]
# Store in dictionary with model name as key
dependent_tables[model.__name__] = primary_keys
return dependent_tables
def print_dependent_tables(model_class, primary_key):
"""
Print dependent tables and their primary keys in a readable format.
Args:
model_class: Django model class
primary_key: Primary key value of the instance
"""
dependent_tables = get_dependent_tables_with_foreign_keys(model_class, primary_key)
if not dependent_tables:
print(f"No dependent tables found for {model_class.__name__} with pk={primary_key}")
return
print(f"\nDependent tables for {model_class.__name__} (pk={primary_key}):")
print("-" * 60)
for table_name, primary_keys in dependent_tables.items():
print(f"\n{table_name}:")
print(f" Primary Keys: {primary_keys}")
print(f" Count: {len(primary_keys)}")
Example Usage
Let's say you have the following models:
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
class Review(models.Model):
book = models.ForeignKey(Book, on_delete=models.CASCADE)
rating = models.IntegerField()
comment = models.TextField()
To find all dependent tables for an Author with primary key 1:
from myapp.models import Author
# Get dependent tables
dependent_tables = get_dependent_tables_with_foreign_keys(Author, 1)
# Print results
print_dependent_tables(Author, 1)
This would output something like:
Dependent tables for Author (pk=1):
------------------------------------------------------------
Book:
Primary Keys: [1, 2, 3]
Count: 3
Review:
Primary Keys: [1, 2, 5, 6]
Count: 4
Use Cases
This functionality is particularly useful for:
- Pre-deletion Analysis: Understanding what will be affected before deleting a record
- Data Migration: Identifying all related records that need to be migrated
- Audit Trails: Tracking dependencies between records
- Cascade Effect Visualization: Understanding the impact of changes
Important Notes
- The
Collectorclass is part of Django's internal API, so it may change between Django versions - This approach only finds direct and indirect foreign key relationships
- It doesn't include reverse foreign key relationships (OneToOne, ManyToMany) unless they're configured with
on_delete=models.CASCADE - The collector respects the
on_deletebehavior defined in your models
Hope this helps!