Inventario uses Django’s ORM with support for PostgreSQL in production and SQLite for development. Database configuration is managed through environment variables.
Database Configuration
PostgreSQL with dj-database-url
The database is configured using dj-database-url for easy environment-based setup:
import dj_database_url
DATABASES = {
'default': dj_database_url.config(
default=f'sqlite:///{BASE_DIR / "db.sqlite3"}',
conn_max_age=600
)
}
Configuration:
- Environment variable:
DATABASE_URL
- Connection pooling:
conn_max_age=600 (10 minutes)
- Fallback: SQLite at
db.sqlite3 if DATABASE_URL not set
PostgreSQL connection string format:
DATABASE_URL=postgresql://username:password@host:port/database_name
Example:
Components:
postgresql:// - Database engine
username:password - Credentials
host:port - Database server location
database_name - Database name
SQLite Fallback (Development)
When DATABASE_URL is not set, Inventario uses SQLite:
default=f'sqlite:///{BASE_DIR / "db.sqlite3"}'
Location: db.sqlite3 in project root
SQLite is suitable for development only. Always use PostgreSQL for production:
- SQLite lacks concurrent write support
- No user management or access control
- Limited performance under load
- File-based storage not suitable for containerized deployments
Database Setup
Development Setup
For local development with SQLite:
# No DATABASE_URL needed
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver
SQLite database is created automatically on first migration.
Production Setup (PostgreSQL)
-
Provision PostgreSQL database
On Railway:
# Railway provides DATABASE_URL automatically when you add PostgreSQL
On Render:
# Add PostgreSQL service in Render dashboard
# Copy DATABASE_URL from database info
Manual setup:
CREATE DATABASE inventario;
CREATE USER inventario_user WITH PASSWORD 'secure_password';
GRANT ALL PRIVILEGES ON DATABASE inventario TO inventario_user;
-
Set environment variable
DATABASE_URL=postgresql://inventario_user:secure_password@localhost:5432/inventario
-
Run migrations
Migrations
Django migrations track database schema changes.
Running Migrations
Apply pending migrations:
The build.sh script automatically runs migrations on deployment:
echo "🗄️ Applying migrations..."
python manage.py migrate
Creating Migrations
After changing models, create migrations:
python manage.py makemigrations
This generates migration files in each app’s migrations/ directory.
Always commit migration files to version control. They ensure database schema stays in sync across environments.
Checking Migration Status
View applied and pending migrations:
python manage.py showmigrations
View SQL for a migration:
python manage.py sqlmigrate <app_name> <migration_number>
Rolling Back Migrations
Rollback to a specific migration:
python manage.py migrate <app_name> <migration_name>
Rollback all migrations for an app:
python manage.py migrate <app_name> zero
Rolling back migrations in production can cause data loss. Always backup the database first.
Database Applications
Inventario includes these database-backed applications:
- applications.usuarios - User management
- applications.cuentas - Account management with custom user model
- applications.proveedores - Supplier records
- applications.productos - Product inventory
- applications.clientes - Customer records
- applications.ventas - Sales transactions
- applications.reportes - Report data
- applications.compras - Purchase orders
- applications.configuracion - System configuration
- applications.devoluciones - Return/refund records
Each application has its own migration directory and models.
Custom User Model
Inventario uses a custom user model:
AUTH_USER_MODEL = 'cuentas.Usuario'
This model is defined in applications.cuentas.models and extends Django’s authentication.
Changing AUTH_USER_MODEL after initial migrations is extremely difficult. This must be set before first migration.
Connection Pooling
Keeps database connections open for 10 minutes:
- Reduces connection overhead
- Improves response times
- Prevents connection exhaustion
Adjust based on:
- Database connection limits
- Application server count
- Expected concurrent users
Formula: max_connections / (workers * servers) ≈ 600 seconds
Database Backups
PostgreSQL Backups
Create a backup:
pg_dump -h host -U username -d database_name -F c -f backup.dump
With DATABASE_URL:
pg_dump $DATABASE_URL -F c -f backup_$(date +%Y%m%d_%H%M%S).dump
Restore from Backup
pg_restore -h host -U username -d database_name -c backup.dump
Restore with -c (clean) drops existing objects. Test on a separate database first.
Automated Backups
On Railway:
- Configure automatic backups in Railway dashboard
- Backups are taken daily by default
On Render:
- Enable automatic backups in database settings
- Choose retention period
Manual automation:
#!/bin/bash
# backup.sh - Daily PostgreSQL backup
BACKUP_DIR="/backups"
DATABASE_URL="$DATABASE_URL"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
pg_dump $DATABASE_URL -F c -f "$BACKUP_DIR/backup_$TIMESTAMP.dump"
# Keep only last 7 days
find $BACKUP_DIR -name "backup_*.dump" -mtime +7 -delete
Run daily with cron:
0 2 * * * /path/to/backup.sh
Database Management
Django Database Shell
Open PostgreSQL shell through Django:
This uses the configured DATABASE_URL automatically.
Inspecting the Database
View all tables:
Describe a table:
Query data:
SELECT * FROM cuentas_usuario LIMIT 10;
Django Shell
Interact with models in Python:
from applications.cuentas.models import Usuario
from applications.productos.models import Producto
# Query users
users = Usuario.objects.all()
print(f"Total users: {users.count()}")
# Query products
products = Producto.objects.filter(activo=True)
print(f"Active products: {products.count()}")
Database Optimization
Indexing
Django automatically creates indexes for:
- Primary keys
- Foreign keys
- Fields with
unique=True
- Fields with
db_index=True
Add custom indexes in models:
class Meta:
indexes = [
models.Index(fields=['campo1', 'campo2']),
]
Query Optimization
Use select_related() for foreign keys:
# Instead of:
products = Producto.objects.all()
for product in products:
print(product.proveedor.nombre) # N+1 queries
# Use:
products = Producto.objects.select_related('proveedor')
for product in products:
print(product.proveedor.nombre) # 1 query
Use prefetch_related() for many-to-many:
ventas = Venta.objects.prefetch_related('productos')
Database Monitoring
Monitor these metrics:
- Connection count
- Query execution time
- Slow queries
- Database size
- Index usage
Enable query logging in development:
# settings.py (development only)
if DEBUG:
LOGGING = {
'version': 1,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'level': 'DEBUG',
},
},
}
Troubleshooting
”No such table” Error
Migrations haven’t been applied:
Connection Refused
Check DATABASE_URL is correct:
Test PostgreSQL connection:
Authentication Failed
Verify credentials in DATABASE_URL:
- Username and password are correct
- User has access to the database
- Database exists
Too Many Connections
Reduce conn_max_age or increase PostgreSQL max_connections:
DATABASES = {
'default': dj_database_url.config(
default=f'sqlite:///{BASE_DIR / "db.sqlite3"}',
conn_max_age=300 # Reduced from 600
)
}
Migration Conflicts
If multiple developers create migrations simultaneously:
python manage.py makemigrations --merge
Database Locked (SQLite)
SQLite locks the entire database for writes. Switch to PostgreSQL for production:
DATABASE_URL=postgresql://...
python manage.py migrate
Data Import/Export
Export Data (JSON)
python manage.py dumpdata > data.json
Export specific app:
python manage.py dumpdata productos > productos.json
Import Data
python manage.py loaddata data.json
loaddata can create duplicate data. Use with caution in production.
Migration from SQLite to PostgreSQL
To migrate from development SQLite to production PostgreSQL:
-
Export data from SQLite
python manage.py dumpdata --natural-foreign --natural-primary \
-e contenttypes -e auth.Permission > data.json
-
Configure PostgreSQL
export DATABASE_URL=postgresql://user:pass@host:5432/dbname
-
Run migrations
-
Import data
python manage.py loaddata data.json
-
Verify data
python manage.py shell
>>> from applications.cuentas.models import Usuario
>>> Usuario.objects.count()