The ORM does not need to track the rows fetched, instead it identifies rows by their primary key to determine, whether to insert or to update (if you don't set force_insert or force_update with save()).
Who can read about this here: http://docs.djangoproject.com/en/1.2/ref/models/instances/#how-django-knows-to-update-vs-insert
This said, it is not necessary or even possible to use a model "read only" since it wouldn't yield any performance improvement.
If you want to optimize, there are however some steps you can try (however only small improvements, so you probably should not optimze until it is really necessary).
For example, call querySet.exists()
resp. querySet.count()
instead of (bool)querySet
resp. len(querySet)
if (and only if) you are not reading from the query set afterwards. Otherwise, don't use exists()/count(), since it will produce an additional query whereas in the latter cache, the actual reading of the query set is free of cost since it is already cached then.
Another measure is to use only() and defer() to restrict the SELECT to the fields that you actually need and select_related() to pre-fetch foreign key relations, if you know you will need them. If you have larger models with many relations and columns, this can give you a significant performance boost.