from types import MethodType
from django.db import models
from django.db.models.query import QuerySet

def applicate_method_list(queryset_object):
    """
    Convert method list to object attribute.
    """
    manager_method_list = getattr(queryset_object, '_manager_method_list')
    for query_method in manager_method_list:
        try:
            getattr(queryset_object, query_method.__name__)
            break
        except AttributeError:
            setattr(queryset_object, query_method.__name__, query_method)
    return queryset_object

def manager_methods_in_queryset(_clone_method):
    """
    Decorator for method '_clone' in object QuerySet
    """
    def _clone_decorator(*args, **kwargs):
        c = _clone_method(*args, **kwargs)
        if not hasattr(c, '_manager_method_list') and hasattr(args[0], '_manager_method_list'):
            c._manager_method_list = getattr(args[0], '_manager_method_list')
            applicate_method_list(c)
        return c
    return _clone_decorator

class ManagerQuerySetDecorator(models.Manager):
    """
    Extends the operation methods of the manager on subqueries QuerySet objects.
    
    The "standard manager" you can only like this:
    SomeModel.object.some_method_manager().all().filter(...). ...
    
    But this does not work:
    SomeModel.object.all().some_method_manager()
    
    And this it works with the use of this class / patches:
    SomeModel.object.all().some_method_manager().filter(...).some_method_manager2(). ... 
    SomeModel.object.some_method_manager().some_method_manager2().some_method_manager3(). ... 
    
    Extension of the methods manager on all child objects "QuerySeth".
    
    Example:
    
    class Animals(models.Model):
        type = models.CharField('Type of animal') # dog, cat, elephant
        color = models.CharField('Colour Name')
        
        objects = AnimalsManager()
        
    class AnimalsManager(ManagerQuerySetDecorator):
    
        def get_dogs(self):
            return self.get_query_set().filter(type='dog')
        get_dogs.queryset_method = True
        
        def get_cats(self):
            return self.get_query_set().filter(type='cat')
        get_cats.queryset_method = True
        
        def get_white_animals(self):
            return self.get_query_set().filter(color='white')
        get_white_animals.queryset_method = True
        
        def get_black_animals(self):
            return self.get_query_set().filter(color='black')
        get_black_animals.queryset_method = True
        
        def get_brown_animals(self):
            return self.get_query_set().filter(color='black')

        
    Animals(type='dog', color='black').save()
    Animals(type='dog', color='bown').save()
    Animals(type='dog', color='white').save()
    Animals(type='cat', color='black').save()
    Animals(type='cat', color='bown').save()
    Animals(type='cat', color='white').save()
    
    animals = Animals.objects.get_black_animals().get_dogs()
    # return list black dogs

    animals = Animals.objects.get_white_animals().get_cats()
    # return list white cats
    
    # When ffff equals False, or not defined (as it is in 
    # the original model manager), we can not perform such an operation:
    animals = Animals.objects.get_cats().get_brown_animals()
    # return:
    # AttributeError: 'QuerySet' object has no attribute 'get_brown_animals'
    
    # but:
    animals = Animals.objects.get_brown_animals().get_cats()
    # return brown cats, because get_cat() present in all parent object instances.
    """
    def __init__(self):
        super(ManagerQuerySetDecorator, self).__init__()
        # Method _clone decoration if it was not previously decorated
        if not getattr(QuerySet._clone, '__name__') == '_clone_decorator':
            QuerySet._clone = manager_methods_in_queryset(QuerySet._clone)
    
    def get_query_set(self):
        qs = super(ManagerQuerySetDecorator, self).get_query_set()
        list_arrtibutes = [getattr(self,attrib_name) for attrib_name in dir(self)]
        for attr in list_arrtibutes:
            if isinstance(attr, MethodType) and hasattr(attr, 'queryset_method') and  attr.queryset_method:
                try:          
                    qs._manager_method_list += [attr]
                except AttributeError:
                    qs._manager_method_list = [attr]
        applicate_method_list(qs)
        return qs