Table of Contents

Django

Useful Django snippets, patterns, and quick references.

Management Commands

Create a custom management command

# myapp/management/commands/my_command.py
from django.core.management.base import BaseCommand
 
class Command(BaseCommand):
    help = "Description of your command"
 
    def add_arguments(self, parser):
        parser.add_argument("name", type=str)
        parser.add_argument("--dry-run", action="store_true")
 
    def handle(self, *args, **options):
        name = options["name"]
        if options["dry_run"]:
            self.stdout.write(f"Would process: {name}")
            return
        self.stdout.write(self.style.SUCCESS(f"Done: {name}"))

QuerySet Tricks

Bulk create with ignore conflicts

MyModel.objects.bulk_create(
    [MyModel(field="value") for _ in range(100)],
    ignore_conflicts=True,
)

Subquery annotation

from django.db.models import OuterRef, Subquery
 
latest_comment = Comment.objects.filter(
    post=OuterRef("pk")
).order_by("-created_at")
 
Post.objects.annotate(
    latest_comment_text=Subquery(latest_comment.values("text")[:1])
)

F expressions for atomic updates

from django.db.models import F
 
Product.objects.filter(pk=1).update(stock=F("stock") - 1)

Models

Abstract base model with timestamps

from django.db import models
 
class TimeStampedModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
 
    class Meta:
        abstract = True

Custom model manager

class PublishedManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(status="published")
 
class Article(TimeStampedModel):
    title = models.CharField(max_length=200)
    status = models.CharField(max_length=20, default="draft")
 
    objects = models.Manager()  # default
    published = PublishedManager()
 
# Usage: Article.published.all()

Views

APIView with proper error handling (DRF)

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
 
class ItemView(APIView):
    def get(self, request, pk):
        try:
            item = Item.objects.get(pk=pk)
        except Item.DoesNotExist:
            return Response(
                {"error": "Item not found"},
                status=status.HTTP_404_NOT_FOUND,
            )
        serializer = ItemSerializer(item)
        return Response(serializer.data)

Testing

Test with fixtures and client

from django.test import TestCase, Client
from django.urls import reverse
 
class ArticleViewTest(TestCase):
    def setUp(self):
        self.client = Client()
        self.article = Article.objects.create(
            title="Test", status="published"
        )
 
    def test_article_detail(self):
        url = reverse("article-detail", args=[self.article.pk])
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "Test")

Override settings in tests

from django.test import TestCase, override_settings
 
class MyTest(TestCase):
    @override_settings(DEBUG=True, CACHES={"default": {"BACKEND": "django.core.cache.backends.dummy.DummyCache"}})
    def test_with_custom_settings(self):
        pass

Useful One-Liners

# Shell plus (needs django-extensions)
python manage.py shell_plus --print-sql
 
# Show all URLs
python manage.py show_urls
 
# Reset migrations for an app
python manage.py migrate myapp zero
 
# Create superuser non-interactively
DJANGO_SUPERUSER_PASSWORD=pass python manage.py createsuperuser --noinput --username admin --email admin@example.com
 
# Dump data as fixture
python manage.py dumpdata myapp.MyModel --indent 2 > fixture.json

Migrations

Squash migrations

python manage.py squashmigrations myapp 0001 0015

Create an empty migration (for data migrations)

python manage.py makemigrations myapp --empty -n my_data_migration

Data migration template

from django.db import migrations
 
 
def forwards(apps, schema_editor):
    MyModel = apps.get_model("myapp", "MyModel")
    for obj in MyModel.objects.filter(status="old"):
        obj.status = "new"
        obj.save(update_fields=["status"])
 
 
def backwards(apps, schema_editor):
    pass  # or reverse the migration
 
 
class Migration(migrations.Migration):
    dependencies = [
        ("myapp", "0015_previous"),
    ]
 
    operations = [
        migrations.RunPython(forwards, backwards),
    ]

Signals

Post-save signal

from django.db.models.signals import post_save
from django.dispatch import receiver
 
@receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)

Django REST Framework

Serializer with nested write support

class AddressSerializer(serializers.ModelSerializer):
    class Meta:
        model = Address
        fields = ["street", "city", "country"]
 
 
class UserSerializer(serializers.ModelSerializer):
    address = AddressSerializer()
 
    class Meta:
        model = User
        fields = ["name", "email", "address"]
 
    def create(self, validated_data):
        address_data = validated_data.pop("address")
        user = User.objects.create(**validated_data)
        Address.objects.create(user=user, **address_data)
        return user

Custom permission

from rest_framework.permissions import BasePermission
 
class IsOwner(BasePermission):
    def has_object_permission(self, request, view, obj):
        return obj.owner == request.user

Pagination

# settings.py
REST_FRAMEWORK = {
    "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
    "PAGE_SIZE": 25,
}
 
# Or custom pagination
from rest_framework.pagination import CursorPagination
 
class CreatedAtCursorPagination(CursorPagination):
    ordering = "-created_at"
    page_size = 50

See Also