Django - how to visualize signals and save overrides? Django - how to visualize signals and save overrides? python python

Django - how to visualize signals and save overrides?


This is not the full solution, but I hope it can be a good starting point. Consider this code:

from django.db import modelsfrom django.db.models.signals import pre_savefrom django.dispatch import receiverclass A(models.Model):    def save(self, *args, **kwargs):        if not self.pk:            C.objects.create()class B(models.Model):    passclass C(models.Model):    b = models.ForeignKey(B, on_delete=models.CASCADE, blank=True)@receiver(pre_save, sender=C)def pre_save_c(sender, instance, **kwargs):    if not instance.pk:        b = B.objects.create()        instance.b = b

We can get the dependencies for the app name list using inspect, django get_models(), and signals in this manner:

import inspectimport refrom collections import defaultdictfrom django.apps import appsfrom django.db.models import signalsRECEIVER_MODELS = re.compile('sender=(\w+)\W')SAVE_MODELS = re.compile('(\w+).objects.')project_signals = defaultdict(list)for signal in vars(signals).values():    if not isinstance(signal, signals.ModelSignal):        continue    for _, receiver in signal.receivers:        rcode = inspect.getsource(receiver())        rmodel = RECEIVER_MODELS.findall(rcode)        if not rmodel:            continue        auto_by_signals = [            '{} auto create -> {}'.format(rmodel[0], cmodel)            for cmodel in SAVE_MODELS.findall(rcode)        ]        project_signals[rmodel[0]].extend(auto_by_signals)for model in apps.get_models():    is_self_save = 'save' in model().__class__.__dict__.keys()    if is_self_save:        scode = inspect.getsource(model.save)        model_name = model.__name__        for cmodel in SAVE_MODELS.findall(scode):            print('{} auto create -> {}'.format(model_name, cmodel))            for smodels in project_signals.get(cmodel, []):                print(smodels)

This gives:

A auto create -> CC auto create -> B

Updated: change method to found overridden save by the instance class dict.

is_self_save = 'save' in model().__class__.__dict__.keys()


(Too long to fit into a comment, lacking code to be a complete answer)

I can't mock up a ton of code right now, but another interesting solution, inspired by Mario Orlandi's comment above, would be some sort of script that scans the whole project and searches for any overridden save methods and pre and post save signals, tracking the class/object that creates them. It could be as simple as a series of regex expressions that look for class definitions followed by any overridden save methods inside.

Once you have scanned everything, you could use this collection of references to create a dependency tree (or set of trees) based on the class name and then topologically sort each one. Any connected components would illustrate the dependencies, and you could visualize or search these trees to see the dependencies in a very easy, natural way. I am relatively naive in django, but it seems you could statically track dependencies this way, unless it is common for these methods to be overridden in multiple places at different times.


If you only want to track models saves, and not interested in other things happening inside overridden save methods and signals, you can use a mechanism like angio. You can register a global post_save receiver, without a sender argument, one that will be called for all model saves, and print the saved model name in that function. Then, write a script to just call save for all existing models. Something like the following could work:

@receiver(models.signals.post_save)def global_post_save(sender, instance, created, *args, **kwargs):    print(' --> ' + str(sender.__name__))from django.apps import appsfor model in apps.get_models():    instance = model.objects.first()    if instance:        print('Saving ' + str(model.__name__))        instance.save()        print('\n\n')

With the following model structure;

class A(models.Model):    ...    def save(self, *args, **kwargs):        B.objects.create()@receiver(post_save, sender=B)def post_save_b(sender, instance, **kwargs):    C.objects.create()

The script would print:

Saving A --> A --> B --> CSaving B --> B --> CSaving C --> C

This is just a basic sketch of what could be done, and can be improved according to the structure of your application. This assumes you already have an entry in the db for each model. Though not changing anything, this approach also saves things in the database, so would be better run on a test db.