Đi theo thứ tự ưu tiên, chỉ xuống raw SQL khi thật sự cần.
Dùng custom QuerySet khi muốn một chuỗi method tái sử dụng và nối được với nhau: Post.objects.published().popular().recent(). Định nghĩa method trên QuerySet rồi export thành Manager qua .as_manager(). Dùng custom Manager khi cần default queryset khác (ví dụ ẩn is_deleted=True cho mọi query), hoặc method tạo object phức tạp như Order.objects.checkout(cart). Còn raw() / connection.cursor() chỉ nên đụng tới khi ORM diễn tả không nổi: window function phức tạp, CTE đệ quy, bulk-upsert đặc thù Postgres.
class PostQuerySet(models.QuerySet):
def published(self):
return self.filter(status='published')
def popular(self):
return self.filter(views__gte=1000)
class Post(models.Model):
# ... fields ...
objects = PostQuerySet.as_manager()
Post.objects.published().popular() # chainableKhi xuống raw SQL, luôn dùng parameterized query (%s, params=[...]) chứ không bao giờ string-format — calque trực tiếp giá trị vào SQL là cửa SQL injection bỏ ngỏ hoàn toàn.
Raw cũng bỏ qua signal, auto_now, validation, nên chỉ phù hợp cho read hoặc bulk operation mà bạn đã hiểu rõ tác dụng phụ.
In that priority order — only drop to raw SQL when truly needed:
- Custom QuerySet when you want chainable reusable methods:
Post.objects.published().popular().recent(). Define methods on aQuerySetthen export it as a Manager via.as_manager(). - Custom Manager when you need a different default queryset (e.g. hide
is_deleted=True) or complex object-creation methods (Order.objects.checkout(cart)). raw()/connection.cursor()only when the ORM cannot express it: complex window functions, recursive CTEs, Postgres-specific bulk-upsert.
class PostQuerySet(models.QuerySet):
def published(self):
return self.filter(status='published')
def popular(self):
return self.filter(views__gte=1000)
class Post(models.Model):
# ... fields ...
objects = PostQuerySet.as_manager()
Post.objects.published().popular() # chainablePitfall: Raw SQL still uses parameterized queries (%s, params=[...]), never string formatting.
And raw bypasses signals / auto_now / validation — only use it for reads or bulk operations where you understand the side-effect cost.