I've also run into O(n) and even O(n*m) # queries situations. They're entirely avoidable by simply sticking closer to the relational model than the object model. Sadly, this is almost always impossible with just Django's ORM. You very quickly end up writing unmanaged models and raw SQL.<p>SQLAlchemy is so much better when it comes to this, since things like joins are always explicit. However, it's rather hard to use it together with the Django ORM bits, especially the admin. One method I want to explore is replacing only the raw SQL bits with SQLAlchemy, and use its reflection to map to the Django ORM models.
Another thing you can do is build caching into managers.<p>Here is one such effort (shameless plug): <a href="https://github.com/stucchio/Guacamole" rel="nofollow">https://github.com/stucchio/Guacamole</a>
Also worth looking at prefetch_related(), which landed in 1.4, which can be used to alleviate common O(n) situations:<p><a href="https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.prefetch_related" rel="nofollow">https://docs.djangoproject.com/en/dev/ref/models/querysets/#...</a><p>I've also used this in the past to do something similar, grabbing the ids of all specified related fields (related to the same model type) and pulling them back in a single query, with the option to only grab certain columns as dicts instead of full model instances if you're only selecting additional data required for specific templates:<p><a href="http://djangosnippets.org/snippets/1117/" rel="nofollow">http://djangosnippets.org/snippets/1117/</a>
<i>"Django makes it extremely easy to generate database queries, which is both a good and bad thing"</i><p>I experienced this when a junior developer made queries in a loop inside template without realizing that it is actually going to be a lot of SQL queries in the end.