- Author:
- Spotted1270
- Posted:
- May 6, 2022
- Language:
- Python
- Version:
- 3.2
- Score:
- 0 (after 0 ratings)
This is a Python 3 re-write for https://djangosnippets.org/snippets/2117/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | 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()
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 1 week ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 2 weeks ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 6 months ago
Comments
Please login first before commenting.