Homunculus Reticulli
Homunculus Reticulli

Reputation: 68466

Django QuerySet monkey patching to add a new attribute to returned models

I am using Django 3.2.

I have a model like this:

class MyModel(models.Model):
    last_valid_field_values = models.TextField(help_text='JSON field of fieldnames and values')
    # ...

I want to be able to monkey patch my queries, so that when fetching MyModel instances, I can added an attribute last_values_dict — which is just the loaded JSON of the last_valid_field_values.

So I would have something like this (pseudocode):

def callback_func(instance):
    instance.last_values_dict = json.loads(instance.last_valid_field_values)
    
    
MyModel.objects.all().apply_some_function_to_monkey_patch(callback_func)

How can I do this? I also think that possibly, this could be done via a generator iterating over the QuerySet?

Upvotes: 2

Views: 266

Answers (1)

aaron
aaron

Reputation: 43088

You can subclass QuerySet to implement apply_some_function_to_monkey_patch and override _fetch_all to run the callback function, similar to how QuerySet.prefetch_related is implemented. You also need to override __init__ and _clone for it to work with filters.

class MyQuerySet(models.QuerySet):
    def __init__(self, model=None, query=None, using=None, hints=None):
        super().__init__(model=model, query=query, using=using, hints=hints)
        self._callback_funcs = ()
        self._callback_done = False

    def apply_some_function_to_monkey_patch(self, *callback_funcs):
        qs = self._chain()
        qs._callback_funcs = qs._callback_funcs + callback_funcs
        return qs

    def _callback(self):
        for callback_func in self._callback_funcs:
            for item in self._result_cache:
                callback_func(item)
        self._callback_done = True

    def _clone(self):
        clone = super()._clone()
        clone._callback_funcs = self._callback_funcs[:]
        return clone

    def _fetch_all(self):
        super()._fetch_all()
        if self._callback_funcs and not self._callback_done:
            self._callback()

Usage:

class MyManager(BaseManager.from_queryset(MyQuerySet)):
    pass


class MyModel(models.Model):
    objects = MyManager()
def callback_func(instance):
    instance.last_values_dict = json.loads(instance.last_valid_field_values)
    
    
MyModel.objects.all().apply_some_function_to_monkey_patch(callback_func)

Upvotes: 1

Related Questions