As a project grows, so do dependencies and event chains, especially in overridden save()
methods and post_save
and pre_save
signals.
Example:
An overridden A.save
creates two related objects to A
- B
and C
. When C
is saved, the post_save
signal is invoked that does something else, etc...
How can these event chins be made more clear? Is there a way to visualize (generate automatically) such chains/flows? I'm not looking for ERD
nor a Class
diagram. I need to be sure that doing one thing one place won't affect something on the other side of the project, so simple visualization would be the best.
EDIT
To be clear, I know that it would be almost impossible to check dynamically generated signals. I just want to check all (not dynamically generated) post_save
, pre_save
, and overridden save
methods and visualize them so I can see immediately what is happening and where when I save
something.
This is not the full solution, but I hope it can be a good starting point. Consider this code:
from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver
class A(models.Model):
def save(self, *args, **kwargs):
if not self.pk:
C.objects.create()
class B(models.Model):
pass
class 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 inspect
import re
from collections import defaultdict
from django.apps import apps
from django.db.models import signals
RECEIVER_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 -> C
C 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 love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With