Login

Custom model manager chaining (Python 3 re-write)

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

  1. Template tag - list punctuation for a list of items by shapiromatron 2 months ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 2 months, 1 week ago
  3. Serializer factory with Django Rest Framework by julio 9 months, 1 week ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 9 months, 4 weeks ago
  5. Help text hyperlinks by sa2812 10 months, 3 weeks ago

Comments

Please login first before commenting.