arundhaj

all that is technology

Using Django ORM Collector to Get Dependent Tables with Foreign Keys

 

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:

  1. Pre-deletion Analysis: Understanding what will be affected before deleting a record
  2. Data Migration: Identifying all related records that need to be migrated
  3. Audit Trails: Tracking dependencies between records
  4. Cascade Effect Visualization: Understanding the impact of changes

Important Notes

  • The Collector class 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_delete behavior defined in your models

Hope this helps!

Comments