Mineo
Notebooks

Running Django Inside Jupyter Notebooks: A Step-by-Step Guide

This guide provides a clear, step-by-step approach to manually integrating Django with Jupyter Notebooks on the MINEO platform, enabling developers to tap into Django’s powerful ORM and models for tasks like interactive data analysis and prototyping.

Diego Garcia 5 min
Running Django Inside Jupyter Notebooks: A Step-by-Step Guide

Django and Jupyter Notebooks are powerful tools on their own—Django for building robust web applications and Jupyter for interactive coding and data analysis. But when you combine them, you unlock a new level of productivity. Imagine querying your Django models directly from a notebook, prototyping new features, or debugging issues interactively. This guide will show you how to run Django inside your existing Python notebooks on MINEO, step by step.

💡 Why Integrate Django with Jupyter Notebooks?

Integrating Django with Jupyter Notebooks allows you to:

  • Access Django’s ORM for interactive data analysis and manipulation. This is very useful to access to DB and keep the state of them thanks to the migrations.
  • Prototype and test new features or fixes in a flexible, cell-based environment.
  • Debug interactively, inspecting models and database queries on the fly.

This setup is especially useful for developers who want to leverage Django’s backend capabilities while enjoying the interactivity of Jupyter.

⚙️ Prerequisites

Before you start, make sure you have:

  • A basic understanding of Django and Jupyter Notebooks.
  • A Django project set up and accessible on MINEO.
  • Django installed in your notebook’s Python environment. If it’s not installed, you can install it with:
# Install Django in your notebook environment
!pip install django

# For specific version (recommended for production)
!pip install django==4.2.7

# If you need additional packages for your Django project
!pip install django psycopg2-binary  # For PostgreSQL
!pip install django mysqlclient       # For MySQL
!pip install django pillow           # For image handling

# Verify installation
import django
print(f"Django version: {django.get_version()}")

📋 Step-by-Step Guide to Running Django in a Notebook

Follow these steps to configure Django in your existing Python notebook:

Step 1: Set Up the Django Environment

In your notebook, you need to manually configure the Django environment. This involves setting the DJANGO_SETTINGS_MODULE environment variable and initializing Django.

import os
import django
from django.conf import settings

# Set the Django settings module
# Replace 'myproject.settings' with your actual project settings module
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')

# Initialize Django
django.setup()

# Verify Django is properly configured
print(f"Django version: {django.get_version()}")
print(f"Settings module: {os.environ.get('DJANGO_SETTINGS_MODULE')}")
print(f"Database engine: {settings.DATABASES['default']['ENGINE']}")
print("✅ Django successfully configured!")

Replace myproject.settings with the actual name of your Django project’s settings module.

Step 2: Add the Django Project Path

If your notebook isn’t in the same directory as your Django project, you must add the project’s directory to the Python path. This ensures Django can find your apps and models.

import sys
import os

# Get current working directory
current_dir = os.getcwd()
print(f"Current directory: {current_dir}")

# Add Django project path to Python path
# Option 1: If your Django project is in a subdirectory
django_project_path = os.path.join(current_dir, 'myproject')

# Option 2: If your Django project is in a different location
# django_project_path = '/path/to/your/django/project'

# Option 3: If your Django project is in the parent directory
# django_project_path = os.path.dirname(current_dir)

# Add the path if it's not already there
if django_project_path not in sys.path:
    sys.path.insert(0, django_project_path)
    print(f"✅ Added Django project path: {django_project_path}")
else:
    print(f"✅ Django project path already in sys.path: {django_project_path}")

# Verify the path exists
if os.path.exists(django_project_path):
    print(f"✅ Django project directory exists")
    # List contents to verify
    contents = os.listdir(django_project_path)
    print(f"Contents: {contents}")
else:
    print(f"❌ Django project directory not found: {django_project_path}")
    print("💡 Use !ls or !pwd to explore the file system and find your project")

Do not forget to replace the django_project_path with the actual path to your Django project on MINEO. If you’re unsure of the path, use !ls or !pwd in a notebook cell to explore the file system.

Step 3: Import and Use Django Models

Once the environment is set up, you can import your Django models and start querying the database.

# Import your Django models
# Replace 'myapp' with your actual app name
from myapp.models import User, Product, Order

# Example model queries
print("=== Django Model Queries ===")

# Get all users
users = User.objects.all()
print(f"Total users: {users.count()}")

# Get specific user
try:
    user = User.objects.get(id=1)
    print(f"User 1: {user.username} ({user.email})")
except User.DoesNotExist:
    print("User with ID 1 not found")

# Filter users
active_users = User.objects.filter(is_active=True)
print(f"Active users: {active_users.count()}")

# Create a new user (example)
new_user = User.objects.create(
    username='notebook_user',
    email='user@example.com',
    first_name='Jupyter',
    last_name='User'
)
print(f"Created new user: {new_user.username}")

# Complex queries with relationships
if 'Product' in locals():
    # Get products with orders
    products_with_orders = Product.objects.filter(order__isnull=False).distinct()
    print(f"Products with orders: {products_with_orders.count()}")
    
    # Aggregate data
    from django.db.models import Count, Avg
    product_stats = Product.objects.aggregate(
        total_products=Count('id'),
        avg_price=Avg('price')
    )
    print(f"Product statistics: {product_stats}")

# Raw SQL queries (if needed)
from django.db import connection

with connection.cursor() as cursor:
    cursor.execute("SELECT COUNT(*) FROM auth_user")
    user_count = cursor.fetchone()[0]
    print(f"User count (raw SQL): {user_count}")

print("✅ Model queries completed successfully!")

Step 4: Handle Asynchronous Code (If Needed)

If your Django project uses asynchronous features, you might encounter a SynchronousOnlyOperation error in the notebook. To fix this, set the following environment variable:

import os

# Allow synchronous operations in async context (development only)
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"

# Alternative approach: Handle async code properly
import asyncio
from asgiref.sync import sync_to_async

# Example of handling async Django operations
async def async_model_query():
    """Example of async model query"""
    # Convert sync Django ORM calls to async
    users = await sync_to_async(list)(User.objects.all())
    user_count = await sync_to_async(User.objects.count)()
    
    print(f"Async query result: {user_count} users found")
    return users

# Run async function in notebook
if hasattr(asyncio, '_nest_patched'):
    # If nest_asyncio is already patched (common in notebooks)
    result = await async_model_query()
else:
    # Patch asyncio for nested event loops (required in Jupyter)
    import nest_asyncio
    nest_asyncio.apply()
    result = await async_model_query()

Important: The DJANGO_ALLOW_ASYNC_UNSAFE setting is for development or testing only. Do not use this in production.

Complete Example Code

Here’s a full snippet combining all the steps:

# Complete Django setup for Jupyter Notebooks
# ===========================================

import os
import sys
import django
from django.conf import settings

print("🚀 Setting up Django in Jupyter Notebook...")

# Step 1: Configure Django environment
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')

# Step 2: Add Django project to Python path (if needed)
django_project_path = '/path/to/your/django/project'  # Update this path
if django_project_path not in sys.path:
    sys.path.insert(0, django_project_path)
    print(f"✅ Added Django project path: {django_project_path}")

# Step 3: Initialize Django
django.setup()
print(f"✅ Django {django.get_version()} initialized successfully!")

# Step 4: Handle async (if needed)
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"

# Step 5: Import and test models
try:
    from myapp.models import MyModel  # Replace with your actual models
    from django.contrib.auth.models import User
    
    print(f"✅ Models imported successfully!")
    print(f"   - Total users in database: {User.objects.count()}")
    
except ImportError as e:
    print(f"⚠️  Could not import models: {e}")
    print("   Make sure your Django project path and app names are correct")

# Step 6: Test management commands
from django.core.management import call_command

try:
    print("\n🔄 Testing Django management commands...")
    
    # Check migration status
    print("📋 Migration status:")
    call_command('showmigrations', verbosity=0)
    
    print("✅ Management commands working!")
    
except Exception as e:
    print(f"❌ Management command error: {e}")

# Step 7: Database connection test
try:
    from django.db import connection
    
    with connection.cursor() as cursor:
        cursor.execute("SELECT 1")
        result = cursor.fetchone()
        
    print(f"✅ Database connection successful!")
    print(f"   Database: {settings.DATABASES['default']['NAME']}")
    
except Exception as e:
    print(f"❌ Database connection failed: {e}")

print("\n🎉 Django setup complete! You can now:")
print("   - Query your models interactively")
print("   - Run management commands")
print("   - Test database operations")
print("   - Prototype new features")

# Example: Quick data exploration
print("\n📊 Quick data exploration example:")
try:
    users = User.objects.all()[:5]  # Get first 5 users
    for user in users:
        print(f"   User: {user.username} (ID: {user.id})")
except Exception as e:
    print(f"   Could not query users: {e}")

🖥️ Running Django Management Commands

When working with Django in a Jupyter Notebook, you’re not limited to just querying models or testing code interactively. You can also execute Django management commands—like running migrations, creating superusers, or collecting static files—directly within your notebook. This capability streamlines your development workflow by allowing you to manage your Django project without switching to a terminal.

To run these commands programmatically, you’ll use Django’s call_command utility from the django.core.management module. Here’s how to do it, step by step.

Step 1: Import the Required Module

After setting up Django in your notebook, import the call_command function:

from django.core.management import call_command
import io
import sys

# Verify Django is set up
print("✅ Django management commands are ready to use")

This utility lets you invoke any built-in or custom Django management command as if you were running it from the command line.

Step 2: Execute Management Commands

With call_command imported, you can run various Django management commands. Below are some practical examples:

Example 1: Running Migrations

To apply all pending migrations to your database, use:

# Run migrations
try:
    print("🔄 Running migrations...")
    call_command('migrate')
    print("✅ Migrations completed successfully!")
except Exception as e:
    print(f"❌ Migration failed: {e}")

# Check migration status
print("\n📋 Migration status:")
call_command('showmigrations')

This is equivalent to running python manage.py migrate in a terminal.

Example 2: Creating a Superuser

To create a superuser for your Django admin interface:

# Method 1: Interactive superuser creation (will prompt for password)
print("🔄 Creating superuser interactively...")
try:
    call_command('createsuperuser')
    print("✅ Superuser created successfully!")
except Exception as e:
    print(f"❌ Superuser creation failed: {e}")

# Method 2: Programmatic superuser creation
from django.contrib.auth import get_user_model

User = get_user_model()

# Check if superuser already exists
if not User.objects.filter(is_superuser=True).exists():
    try:
        superuser = User.objects.create_superuser(
            username='admin',
            email='admin@example.com',
            password='your_secure_password_here'  # Change this!
        )
        print(f"✅ Superuser '{superuser.username}' created programmatically!")
    except Exception as e:
        print(f"❌ Superuser creation failed: {e}")
else:
    print("ℹ️  Superuser already exists")

Note: The interactive approach is more secure as it prompts for the password without storing it in code. The programmatic approach is useful for automation but requires careful password management.

Example 3: Collecting Static Files

To gather all static files into the STATIC_ROOT directory:

# Collect static files
try:
    print("🔄 Collecting static files...")
    call_command('collectstatic', interactive=False, clear=True)
    print("✅ Static files collected successfully!")
except Exception as e:
    print(f"❌ Static file collection failed: {e}")

# Alternative: With confirmation
print("\n🔄 Collecting static files with confirmation...")
call_command('collectstatic', clear=True)  # Will prompt for confirmation

Note: The interactive=False flag skips the confirmation prompt that would normally ask if you’re sure you want to proceed. The clear=True option removes existing files before collecting new ones.

Example 4: Running Custom Management Commands

If your Django project includes custom management commands (e.g., defined in myapp/management/commands/my_custom_command.py), you can run them like this:

# Example: Running a custom management command
try:
    print("🔄 Running custom command...")
    
    # Run custom command without arguments
    call_command('my_custom_command')
    
    # Run custom command with arguments
    call_command('my_custom_command', '--option1', 'value1', '--flag')
    
    # Run custom command with keyword arguments
    call_command('my_custom_command', 
                option1='value1', 
                flag=True, 
                verbosity=2)
    
    print("✅ Custom command completed successfully!")
    
except Exception as e:
    print(f"❌ Custom command failed: {e}")

# List all available management commands
print("\n📋 Available management commands:")
from django.core.management import get_commands
commands = get_commands()
for cmd_name in sorted(commands.keys()):
    print(f"  - {cmd_name}")

Replace my_custom_command with the name of your actual custom command.

Step 3: Managing Output and Errors

When you execute a management command in a notebook, its output (e.g., migration logs or success messages) appears directly in the cell’s output area. Errors, such as database connection issues or migration conflicts, will also be displayed, making it easy to debug interactively.

If you need to capture the output for further processing or logging, redirect it to a string buffer using Python’s io.StringIO. Here’s an example:

import io
import sys
from contextlib import redirect_stdout, redirect_stderr

# Capture command output
def capture_command_output(command_name, *args, **kwargs):
    """Capture the output of a Django management command"""
    
    # Create string buffers for stdout and stderr
    stdout_buffer = io.StringIO()
    stderr_buffer = io.StringIO()
    
    try:
        # Redirect output to buffers
        with redirect_stdout(stdout_buffer), redirect_stderr(stderr_buffer):
            call_command(command_name, *args, **kwargs)
        
        # Get the captured output
        stdout_content = stdout_buffer.getvalue()
        stderr_content = stderr_buffer.getvalue()
        
        return {
            'success': True,
            'stdout': stdout_content,
            'stderr': stderr_content
        }
        
    except Exception as e:
        return {
            'success': False,
            'error': str(e),
            'stdout': stdout_buffer.getvalue(),
            'stderr': stderr_buffer.getvalue()
        }

# Example usage
print("🔄 Capturing migration output...")
result = capture_command_output('showmigrations')

if result['success']:
    print("✅ Command executed successfully!")
    print("\n📋 Migration Status:")
    print(result['stdout'])
else:
    print(f"❌ Command failed: {result['error']}")
    if result['stderr']:
        print(f"Error details: {result['stderr']}")

# You can also analyze the output
migration_lines = result['stdout'].split('\n') if result['success'] else []
applied_migrations = [line for line in migration_lines if '[X]' in line]
pending_migrations = [line for line in migration_lines if '[ ]' in line]

print(f"\n📊 Analysis:")
print(f"  Applied migrations: {len(applied_migrations)}")
print(f"  Pending migrations: {len(pending_migrations)}")

This approach is handy for automating workflows or analyzing command results within your notebook.

✅ Tips and Best Practices

To make the most of this setup, keep these tips in mind:

  • Environment Management: Ensure your notebook is using the correct Python environment where Django is installed. Check MINEO’s kernel or environment options if multiple are available.
  • Path Handling: If possible, use relative paths or environment variables to make your code more flexible and easier to share.
  • Permissions: Be aware of any platform-specific restrictions on file access or database connections. Consult MINEO’s documentation if you encounter permission issues.
  • Debugging: If you face import errors, use print(sys.path) to verify that your project path is correctly added.
  • Performance: Be mindful of resource usage, especially when running large queries or working with big datasets, to avoid slowing down your notebook.

🎯 Conclusion

Running Django inside your Jupyter Notebooks on MINEO is a powerful way to interact with your web application’s data and logic. By following these steps, you can set up the Django environment manually, giving you full control and flexibility. Remember to handle paths carefully, manage your environment, and use the tips provided to avoid common pitfalls.

Now that you’re equipped with this knowledge, dive into your notebook and start exploring your Django project in a whole new way.

Happy coding!