from django.db.models import Manager
from django.db.models.query import QuerySet
from django.db.models.fields.related import SingleRelatedObjectDescriptor


class _DerivedNamesMixin(object):
    def _get_derived_names(self):
        return [k for k, v in self.model.__dict__.iteritems()
                if isinstance(v, SingleRelatedObjectDescriptor)
                and issubclass(v.related.model, self.model)]


class DerivedQuerySet(_DerivedNamesMixin, QuerySet):
    def iterator(self):
        prefetched = super(DerivedQuerySet, self).select_related(
            *self._get_derived_names())
        for obj in super(DerivedQuerySet, prefetched).iterator():
            yield self.__get_derived(obj)

    def __get_derived(self, instance):
        from django.core.exceptions import ObjectDoesNotExist
        for derived_name in self._get_derived_names():
            try:
                return getattr(instance, derived_name)
            except ObjectDoesNotExist:
                pass
        return instance


class DerivedManager(_DerivedNamesMixin, models.Manager):
    def get_query_set(self, *args, **kwargs):
        return DerivedQuerySet(self.model, using=self._db).select_related(
            *self._get_derived_names())