from django.db.models.manager import Manager from django.db.models.query import QuerySet def manager_from(*mixins_and_funcs, **kwargs): """ Returns a Manager instance with extra methods, also available and chainable on generated querysets. (param) mixins_and_funcs: Each mixin can be either a class or a function. The generated manager and associated queryset subclasses extend the mixin classes and include the mixin functions (as methods). (kwarg) queryset_cls: The base queryset class to extend from (`django.db.models.query.QuerySet` by default). (kwarg) manager_cls: The base manager class to extend from (`django.db.models.manager.Manager` by default). """ base_queryset = kwargs.get("queryset_cls", QuerySet) manager_class = kwargs.get("manager_cls", Manager) # Collect the mixin classes and methods into separate variables. bases = [base_queryset] methods = {} for mixin_or_func in mixins_and_funcs: # If the mixin is a class (all classes in Python 3+ inherit from the base `type` class). if isinstance(mixin_or_func, type): # Add it to our bases list. bases.append(mixin_or_func) # If it is not a class, is it a function? else: try: methods.update({mixin_or_func.__name__, mixin_or_func}) except AttributeError: # If you pass in a variable of a bool data type, for example, it will raise an attribute error! The __name__ property is only available on classes and methods (?). raise TypeError( f"Mixin must be class or function, not {mixin_or_func.__class__}" ) kwargs_as_tuple = tuple(iter(kwargs.items())) args_id = hash(mixins_and_funcs + kwargs_as_tuple) # Create the QuerySet subclass: name it deterministically (same set of arguments returns the same queryset class name), add base classes and class methods. new_queryset_class = type( f"Queryset_{args_id}", tuple(bases), methods ) # Create the Manager subclass. bases[0] = manager_class new_manager_class = type( f"Manager_{args_id}", tuple(bases), methods ) # And finally, override new manager's get_query_set. super_get_queryset = manager_class.get_queryset def get_queryset(self): # First honor the super manager's get_query_set qs = super_get_queryset(self) # And then try to bless the returned queryset by reassigning it to the newly created Queryset class, though this may not be feasible. if not issubclass(new_queryset_class, qs.__class__): raise TypeError( "QuerySet subclass conflict: cannot determine a unique class for queryset instance" ) qs.__class__ = new_queryset_class return qs new_manager_class.get_queryset = get_queryset return new_manager_class()