Django cookbook · Beginner
Write an idempotent management command
Operational scripts (cleanups, backfills) are run ad hoc from shells and behave differently each time.
Approach
Put the script in a management command with arguments, a dry-run mode, batching and a clear summary. Make it idempotent so re-running after a failure is safe.
Solution
# shop/management/commands/expire_carts.py
from datetime import timedelta
from django.core.management.base import BaseCommand
from django.utils import timezone
from shop.models import Cart
class Command(BaseCommand):
help = "Delete carts that haven't been updated for a number of days."
def add_arguments(self, parser):
parser.add_argument("--days", type=int, default=30)
parser.add_argument("--dry-run", action="store_true")
parser.add_argument("--batch-size", type=int, default=1000)
def handle(self, *args, days, dry_run, batch_size, **options):
cutoff = timezone.now() - timedelta(days=days)
stale = Cart.objects.filter(updated_at__lt=cutoff, order__isnull=True)
total = stale.count()
if dry_run:
self.stdout.write(f"Would delete {total} carts older than {cutoff:%Y-%m-%d}")
return
deleted = 0
while ids := list(stale.values_list("id", flat=True)[:batch_size]):
deleted += Cart.objects.filter(id__in=ids).delete()[0]
self.stdout.write(self.style.SUCCESS(f"Deleted {deleted} rows for {total} carts"))
Pitfalls
- One giant
delete()on millions of rows, locking tables for a long time. - Printing with
print()instead ofself.stdout, which breakscall_commandoutput capture in tests. - No dry-run for destructive commands.
Variations
- Schedule it with cron, Kubernetes CronJobs or a periodic task, and alert on failures.
- Test it with
call_command("expire_carts", days=1, stdout=out).
Further reading: Custom management commands
Reference: https://docs.djangoproject.com/en/6.0/howto/custom-management-commands/
More Django recipes
- Add a required column without downtime
- Add full-text search with PostgreSQL
- Add health checks and structured logging
- Avoid N+1 queries with select_related and prefetch_related
- Cache expensive queries and invalidate on change
- Call external APIs concurrently in async views
- Configure settings from environment variables
- Enforce rules with database constraints