Senior Tips & Tricks

A grab-bag of the habits, shortcuts and gotchas that separate a seasoned Django developer from someone who just knows the docs.

None of these is big enough for its own chapter, but together they are the difference between code that works and code a senior would sign off on. Consider this your field notebook.

Query and data

  • See the SQL. print(qs.query) shows the exact SQL a queryset will run — your first move when a query behaves oddly.
  • .only() / .defer() fetch or skip specific columns when a table has a fat TextField you rarely need.
  • bulk_create / bulk_update collapse thousands of INSERTs into one round-trip. update() and delete() on a queryset skip save() and signals — fast, but know what you are bypassing.
  • get_or_create / update_or_create handle the "insert if missing" race cleanly — back them with a unique constraint.

Debugging and shell

  • manage.py shell_plus (django-extensions) auto-imports every model. Add --print-sql to watch every query.
  • Django Debug Toolbar in dev makes N+1 problems visible at a glance.

Correctness habits

  • Timezone-aware datetimes: always from django.utils import timezone; timezone.now(), never datetime.now().
  • update_fields on save() writes only what changed — less contention, and it avoids clobbering a field another process just updated.
  • Reverse URLs, never hardcode. reverse("order-detail", args=[pk]) and {% url %} survive route changes; string paths do not.

The meta-tip: optimise for the reader. The Django that ages well is boring, explicit, and easy to delete. Clever is a liability the next person — often future-you — has to reverse-engineer at 2am.

Example

Example · python
from django.utils import timezone
from django.urls import reverse
from .models import Article, Tag

# --- See the actual SQL a queryset will run (do this FIRST when confused) ---
qs = Article.objects.filter(status="published").select_related("author")
print(qs.query)     # SELECT ... FROM blog_article INNER JOIN ... WHERE status = 'published'

# --- Fetch only the columns you need; skip a fat body column ---
headlines = Article.objects.only("id", "title").filter(status="published")
light = Article.objects.defer("body")            # everything except the big TextField

# --- One round-trip instead of thousands ---
Tag.objects.bulk_create([Tag(name=n) for n in ("django", "orm", "async")])
Article.objects.filter(views__gt=1000).update(featured=True)  # skips save()/signals

# --- Race-safe upsert, backed by a unique constraint ---
tag, created = Tag.objects.get_or_create(name="python", defaults={"color": "blue"})

# --- Always timezone-aware, always reverse() ---
article = Article.objects.create(title="Hi", published_at=timezone.now())
url = reverse("article-detail", args=[article.pk])   # survives URL refactors

# --- Write only what changed: cheaper, avoids clobbering concurrent updates ---
article.title = "Edited"
article.save(update_fields=["title", "updated_at"])

When to use it

  • A developer uses select_for_update() inside a transaction to prevent race conditions in concurrent purchase flows.
  • A team uses django-extensions's shell_plus to get auto-imported models and utilities in the Django shell.
  • A developer uses Django's system check framework to write a custom check that validates required environment variables at startup.

More examples

Use only() to reduce column reads

Fetches only the columns needed for a feed page, avoiding the large body column and reducing query payload.

Example · python
# Only fetch needed columns for a feed
posts = (
    Post.objects
    .only("id", "title", "slug", "published_at", "author_id")
    .select_related("author")
    .filter(draft=False)
    .order_by("-published_at")[:25]
)

Custom system check

A custom system check that reports missing environment variables when running any manage.py command.

Example · python
from django.core.checks import Error, register
import os

@register()
def check_required_env_vars(app_configs, **kwargs):
    errors = []
    required = ["STRIPE_SECRET_KEY", "SENDGRID_API_KEY"]
    for var in required:
        if not os.environ.get(var):
            errors.append(Error(f"{var} is not set", id="myapp.E001"))
    return errors

Reusable test fixture factory

Uses factory_boy to define reusable test data factories so tests create realistic objects with minimal code.

Example · python
# tests/factories.py using factory_boy
import factory
from django.contrib.auth import get_user_model
from blog.models import Post

class UserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = get_user_model()
    username = factory.Sequence(lambda n: f"user{n}")
    email = factory.LazyAttribute(lambda u: f"{u.username}@example.com")

class PostFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Post
    title = factory.Faker("sentence", nb_words=5)
    author = factory.SubFactory(UserFactory)

Discussion

  • Be the first to comment on this lesson.
Senior Tips & Tricks — Django | SoundsCode