Implement smart database auto-detection for local/Railway compatibility

**Smart Database Configuration:**
- Auto-detects environment: Railway vs Local development
- Tests PostgreSQL connection before using it
- Falls back to SQLite if PostgreSQL unavailable
- Preserves Railway DATABASE_URL when present

**Environment Detection Logic:**
1. Railway: Uses Railway's DATABASE_URL (PostgreSQL)
2. Local with DATABASE_URL: Uses specified database
3. Local with PostgreSQL running: Uses PostgreSQL
4. Local without PostgreSQL: Falls back to SQLite

**New Management Command:**
- `check_db`: Diagnose database configuration and connection
- Shows current engine, connection status, table count
- Provides troubleshooting tips for PostgreSQL setup

**Solves the Compatibility Problem:**
- Local development: Works without PostgreSQL setup (SQLite fallback)
- Railway production: Always uses PostgreSQL (via DATABASE_URL)
- No more "works locally but fails on Railway" issues
- No more "needs PostgreSQL locally to test Railway compatibility"

**Benefits:**
- Seamless development experience
- Production-development parity when PostgreSQL available
- Graceful degradation to SQLite when needed
- Zero configuration required for basic development

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude 2025-07-12 10:10:42 +05:30
parent 4e7e43c436
commit 58d56c3200
2 changed files with 166 additions and 6 deletions

View File

@ -0,0 +1,108 @@
from django.core.management.base import BaseCommand
from django.conf import settings
from django.db import connection
import os
class Command(BaseCommand):
help = 'Check current database configuration and connection'
def handle(self, *args, **options):
self.stdout.write("🔍 Database Configuration Check")
self.stdout.write("=" * 40)
# Environment detection
is_railway = bool(os.environ.get('RAILWAY_ENVIRONMENT'))
database_url = os.environ.get('DATABASE_URL', '')
self.stdout.write(f"Environment: {'Railway' if is_railway else 'Local Development'}")
self.stdout.write(f"DATABASE_URL set: {'Yes' if database_url else 'No'}")
if database_url:
# Mask password in URL for display
masked_url = database_url
if '@' in masked_url and '://' in masked_url:
parts = masked_url.split('://')
if len(parts) == 2:
scheme = parts[0]
rest = parts[1]
if '@' in rest:
auth_part, host_part = rest.split('@', 1)
if ':' in auth_part:
user, password = auth_part.split(':', 1)
masked_url = f"{scheme}://{user}:***@{host_part}"
self.stdout.write(f"DATABASE_URL: {masked_url}")
# Current Django database configuration
db_config = settings.DATABASES['default']
engine = db_config['ENGINE']
self.stdout.write(f"\\nCurrent Django Configuration:")
self.stdout.write(f"Engine: {engine}")
if 'postgresql' in engine:
self.stdout.write(f"Database: {db_config.get('NAME', 'N/A')}")
self.stdout.write(f"Host: {db_config.get('HOST', 'N/A')}")
self.stdout.write(f"Port: {db_config.get('PORT', 'N/A')}")
self.stdout.write(f"User: {db_config.get('USER', 'N/A')}")
elif 'sqlite' in engine:
self.stdout.write(f"Database file: {db_config.get('NAME', 'N/A')}")
# Test connection
self.stdout.write(f"\\n🔌 Testing Database Connection...")
try:
with connection.cursor() as cursor:
if 'postgresql' in engine:
cursor.execute("SELECT version();")
version = cursor.fetchone()[0]
self.stdout.write(f"✅ PostgreSQL Connection: {version}")
elif 'sqlite' in engine:
cursor.execute("SELECT sqlite_version();")
version = cursor.fetchone()[0]
self.stdout.write(f"✅ SQLite Connection: {version}")
# Check if tables exist
if 'postgresql' in engine:
cursor.execute("""
SELECT COUNT(*) FROM information_schema.tables
WHERE table_schema = 'public'
""")
else:
cursor.execute("""
SELECT COUNT(*) FROM sqlite_master
WHERE type='table' AND name NOT LIKE 'sqlite_%'
""")
table_count = cursor.fetchone()[0]
self.stdout.write(f"📊 Database tables: {table_count}")
if table_count == 0:
self.stdout.write("⚠️ No tables found. Run: python manage.py migrate")
except Exception as e:
self.stdout.write(f"❌ Connection failed: {e}")
if 'postgresql' in engine:
self.stdout.write("\\n💡 PostgreSQL Connection Tips:")
self.stdout.write("1. Install PostgreSQL: brew install postgresql")
self.stdout.write("2. Start PostgreSQL: brew services start postgresql")
self.stdout.write("3. Create database: createdb netcop_hub")
self.stdout.write("4. Create user: createuser netcop_user -P")
self.stdout.write("5. Or use Docker: docker run --name netcop-postgres -e POSTGRES_DB=netcop_hub -e POSTGRES_USER=netcop_user -e POSTGRES_PASSWORD=netcop_pass -p 5432:5432 -d postgres:15")
# Module availability check
self.stdout.write(f"\\n📦 Module Availability:")
try:
import psycopg2
self.stdout.write("✅ psycopg2 (PostgreSQL driver) available")
except ImportError:
self.stdout.write("❌ psycopg2 not available")
try:
import sqlite3
self.stdout.write("✅ sqlite3 available")
except ImportError:
self.stdout.write("❌ sqlite3 not available")
self.stdout.write("\\n" + "=" * 40)
self.stdout.write("Database check complete!")

View File

@ -89,18 +89,70 @@ WSGI_APPLICATION = 'netcop_hub.wsgi.application'
import dj_database_url import dj_database_url
# Use DATABASE_URL if available (Railway, Heroku, etc.) # Smart database configuration: Auto-detect environment
if config('DATABASE_URL', default=''): database_url = config('DATABASE_URL', default='')
if database_url:
# Parse the provided DATABASE_URL
DATABASES = { DATABASES = {
'default': dj_database_url.parse(config('DATABASE_URL')) 'default': dj_database_url.parse(database_url)
} }
else: elif config('RAILWAY_ENVIRONMENT', default=''):
# Railway environment without DATABASE_URL (shouldn't happen, but fallback)
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.sqlite3', 'ENGINE': 'django.db.backends.postgresql',
'NAME': BASE_DIR / 'db.sqlite3', 'NAME': config('PGDATABASE', default='railway'),
'USER': config('PGUSER', default='postgres'),
'PASSWORD': config('PGPASSWORD', default=''),
'HOST': config('PGHOST', default='localhost'),
'PORT': config('PGPORT', default='5432'),
} }
} }
else:
# Local development: Try PostgreSQL first, fallback to SQLite
try:
import psycopg2
# Test if PostgreSQL is actually available
try:
# Quick connection test
test_conn = psycopg2.connect(
host='localhost',
database='netcop_hub',
user='netcop_user',
password='netcop_pass',
port='5432',
connect_timeout=3
)
test_conn.close()
# PostgreSQL works - use it
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'netcop_hub',
'USER': 'netcop_user',
'PASSWORD': 'netcop_pass',
'HOST': 'localhost',
'PORT': '5432',
}
}
except (psycopg2.OperationalError, psycopg2.Error):
# PostgreSQL not available - fallback to SQLite
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
except ImportError:
# psycopg2 not available - use SQLite
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation # Password validation