When to leave the ORM
The ORM covers most work, and annotate, Func, Q and F go a long way. Reach for a custom field or raw SQL when you convert the same value over and over, need a database-specific feature, or have measured that the generated query is too slow.
| Need | Tool |
|---|---|
| Store a custom type | Field subclass |
Add a filter such as tags__has | custom Lookup |
| One SQL expression inside a queryset | RawSQL, Func |
| Complex query that returns models | Model.objects.raw() |
| Report, DDL, bulk write | connection.cursor() |
💡 Raw SQL is not portable across SQLite, PostgreSQL and MySQL. Cover it with tests and keep it in one place.
A custom Field
A field translates between a Python object and a database type. Inherit from the closest built-in field so the column type is ready, then override the conversions.
from django.db import models
class TagListField(models.TextField):
description = "A list of tags stored as comma-separated text"
def from_db_value(self, value, expression, connection):
if value is None:
return value
return [t for t in value.split(",") if t]
def to_python(self, value):
if value is None or isinstance(value, list):
return value
return [t for t in value.split(",") if t]
def get_prep_value(self, value):
if value is None or isinstance(value, str):
return value
return ",".join(value)
class Post(models.Model):
title = models.CharField(max_length=100)
tags = TagListField(default=list)