When working with Django, it’s common to use multiple databases for different purposes. In larger applications, one database might store user data, while another database stores blog content, for example. However, when you need to establish relationships between models stored in different databases (a cross-database relationship), things get tricky since Django does not natively support foreign key relationships across databases.
In this blog post, we’ll go over how to set up and manage cross-database relationships in Django, focusing on an example where we have a Blog model in the blog_db database and a User model (and other default models like auth and sessions) in the default database.
1. Scenario: Cross-Database Relationship
Imagine you’re building a blogging platform where:
- The
Usermodel (which is part of Django’s defaultauthapp) is stored in thedefaultdatabase. - The
Blogmodel is stored in a separateblog_dbdatabase for better scalability and separation of concerns.
The goal is to create a relationship between a User and a Blog, where a Blog post is authored by a user, even though they are stored in different databases.
2. Setting Up Multiple Databases in Django
First, let’s set up Django to use multiple databases. In settings.py, we define two databases:
defaultfor the Django built-in models (User,Session, etc.).blog_dbfor the blog content.
Database Configuration in settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'default_db',
'USER': 'user',
'PASSWORD': 'password',
'HOST': 'localhost',
'PORT': '5432',
},
'blog_db': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'blog_db',
'USER': 'user',
'PASSWORD': 'password',
'HOST': 'localhost',
'PORT': '5433',
}
}3. Creating a Custom Database Router
Django provides a way to manage which database should be used for a given model by implementing a custom database router. The router allows you to control which database should handle the read and write operations for a particular model or app.
Create a new file called routers.py in your project:
class BlogDatabaseRouter:
def db_for_read(self, model, **hints):
"""Direct read queries to the appropriate database."""
if model._meta.app_label == 'blog':
return 'blog_db'
return 'default'
def db_for_write(self, model, **hints):
"""Direct write queries to the appropriate database."""
if model._meta.app_label == 'blog':
return 'blog_db'
return 'default'
def allow_relation(self, obj1, obj2, **hints):
"""Allow relationships if both models are in the same database."""
if obj1._state.db == obj2._state.db:
return True
return False
def allow_migrate(self, db, app_label, model_name=None, **hints):
"""Ensure models are migrated to the correct database."""
if app_label == 'blog':
return db == 'blog_db'
return db == 'default'This router directs all operations for models in the blog app to the blog_db database, and all other operations (like user authentication, etc.) will go to the default database.
4. Defining the Models: Blog and User
Now, let’s define the Blog model in the blog app and relate it to the User model (which is part of Django’s auth app in the default database).
User Model (in default database)
Django comes with a built-in User model that you can use directly. You don’t need to modify the User model itself unless you need custom fields.
Blog Model (in blog_db database)
Create a Blog model that will store posts authored by users. Since the User model resides in the default database, we will manually store the user_id as a reference to the User model’s primary key in the Blog model.
# models.py in the 'blog' app (stored in 'blog_db')
from django.db import models
class Blog(models.Model):
user_id = models.IntegerField() # Store the User ID here (reference to User model)
title = models.CharField(max_length=255)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
def get_user(self):
"""Manually fetch the User from the default database."""
from django.contrib.auth.models import User
return User.objects.using('default').get(id=self.user_id)
def __str__(self):
return f'Blog post titled "{self.title}" by User {self.user_id}'In this Blog model:
- We use an
IntegerField(user_id) to store the reference to theUsermodel’sid. - The
get_user()method manually fetches theUserinstance from thedefaultdatabase by usingUser.objects.using('default').
5. Django Signals: Automatically Create Blog Entries
Django signals can be used to automate the creation of Blog entries when a User is created. This ensures that when a new user is added to the default database, a corresponding Blog entry is created in the blog_db database.
Signal to Create Blog Entry on User Creation
You can set up a signal in the users app (or wherever you have the User model logic):
# signals.py in the 'users' app
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User
from blog.models import Blog
@receiver(post_save, sender=User)
def create_blog_entry(sender, instance, created, **kwargs):
"""Automatically create a Blog entry when a User is created."""
if created:
# Create an initial blog entry in 'blog_db' for the new User
Blog.objects.using('blog_db').create(user_id=instance.id, title='My First Blog', content='Welcome to the platform!')6. Migrations
Once your models and database router are set up, you’ll need to create and apply migrations for both databases.
- Create Migrations for the Default Database (for the
Usermodel and Django’s built-in models likeauth):
python manage.py makemigrations --database=default
python manage.py migrate --database=default- Create Migrations for the Blog App (stored in
blog_db):
python manage.py makemigrations blog --database=blog_db
python manage.py migrate blog --database=blog_db7. Using Cross-Database Data
When querying the Blog model, you can fetch all the blog posts and manually retrieve the associated User data by using the get_user() method:
# Fetch all blog posts and their authors
blogs = Blog.objects.using('blog_db').all()
for blog in blogs:
user = blog.get_user() # Fetch the associated user from the default database
print(f'Blog Title: {blog.title}, Author: {user.username}, Content: {blog.content}')8. Conclusion: Best Practices and Considerations
Using multiple databases in Django can be powerful, but it comes with trade-offs. Here are some key considerations:
- Manual Foreign Keys: Since Django does not support cross-database foreign keys natively, you’ll need to manage references manually using custom fields like
user_idin theBlogmodel. Theget_user()method ensures the relationship works, even though the data resides in different databases. - Database Routers: A custom database router directs operations to the correct database based on the model’s app label. This ensures that
Blogoperations go to theblog_dband user-related operations go to thedefaultdatabase. - Performance: Querying across databases can introduce performance overhead, especially if you are fetching large amounts of data. Be mindful of database joins and consider denormalizing data or caching when necessary.
- Data Integrity: Make sure your models stay in sync. Using Django signals to create related objects across databases can help maintain data integrity.
By following this pattern, you can effectively manage multiple databases and set up cross-database relationships in Django, all while leveraging the framework’s powerful ORM and routing system.
This approach gives you flexibility and scalability for managing complex data models across different databases, ensuring your app can grow while maintaining performance and data consistency.