Instructions: Set your environment variables, install graphviz, and run.
Finds all models in your installed apps and makes a handsome graph of your their dependencies based on foreignkey relationships. Django models have green outlines, yours have purple. The edge styling could be changed based on edge type.
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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | from django.conf import settings
from django.db.models import Model, ForeignKey, ManyToManyField, OneToOneField
def get_all_models():
all_models = set()
for app in settings.INSTALLED_APPS:
try:
models = __import__(app + '.models', {}, {}, 'models')
except ImportError:
continue
for attr in dir(models):
obj = getattr(models, attr)
if isinstance(obj, object) and \
hasattr(obj, '__bases__') and \
Model in obj.__bases__:
all_models.add(obj)
return all_models
def get_relns(all_models):
foreignkeys = []
one_to_one = []
many_to_many = []
for model in all_models:
for field in model._meta.fields:
if isinstance(field, OneToOneField): # must come before FK
to = field.rel.to
if to not in all_models:
raise ValueError
print "%s o2o to %s" % (repr(model), repr(to))
one_to_one.append((model, to))
elif isinstance(field, ForeignKey):
to = field.rel.to
if to not in all_models:
raise ValueError
print "%s fk to %s" % (repr(model), repr(to))
foreignkeys.append((model, to))
elif isinstance(field, ManyToManyField):
to = field.rel.to
if to not in all_models:
raise ValueError
print "%s m2m to %s" % (repr(model), repr(to))
many_to_many.append((model, to))
return foreignkeys, one_to_one, many_to_many
def class_name(cls):
name = repr(cls)
name = name[name.index("'")+1:]
name = name[:name.index("'")]
return quoted(name)
# A lot of code below this line is copied from some stuff I wrote for my paper.
# Should probably be cleaned up.
import os, collections
BG_COLOR = 'black'
FG_COLOR = 'white'
RED = '"#B30000"'
GREEN = '"#008F00"'
PURPLE = '"#24006B"'
YELLOW = '"#B38F00"'
GREY = '"#8F8F8F"'
COLORS = [YELLOW, GREEN, PURPLE, RED]
def quoted(s):
return '"%s"' % str(s)
def nodename(state):
return quoted(state[1]) if (state[1] != '') else quoted('start')
def attrs_string(attrs):
return '[%s]' % ', '.join(["%s=%s" % (k, v) for k, v in attrs.iteritems()])
def model_attrs_string(model, extra_node_attrs, labels):
attrs = {}
attrs['color'] = GREEN if 'django' in repr(model) else PURPLE
if not labels:
attrs['label'] = '""'
attrs.update(extra_node_attrs.get(model, {}))
return attrs_string(attrs)
def edge_attrs_string(c):
return attrs_string({
'label': quoted(c),
})
def fg_attrs_string(extra={}):
attrs = {
'color': FG_COLOR,
'fontcolor': FG_COLOR
}
attrs.update(extra)
return attrs_string(attrs)
def relns_to_dot(models, foreignkeys, one_to_one, many_to_many, extra_node_attrs={}, **kwargs):
edge_len = kwargs.pop('edgelen', 3)
labels = kwargs.pop('labels', True)
result = []
result.append('digraph django_model_relationships {')
result.append(' graph [bgcolor=%s];' % BG_COLOR)
result.append(' node %s;' % fg_attrs_string())
result.append(' edge %s;' % fg_attrs_string({'len': 3}))
result.append(' rankdir=LR;')
result.append(' size="30,20!";')
for model in sorted(models):
result.append(' %s %s;' % (class_name(model), model_attrs_string(model, extra_node_attrs, labels)))
for source, dest in foreignkeys:
edge_label = 'fk'
result.append(' %s -> %s %s;' % (class_name(source), class_name(dest), edge_attrs_string(edge_label)))
for source, dest in one_to_one:
edge_label = 'o2o'
result.append(' %s -> %s %s;' % (class_name(source), class_name(dest), edge_attrs_string(edge_label)))
for source, dest in many_to_many:
edge_label = 'm2m'
result.append(' %s -> %s %s;' % (class_name(source), class_name(dest), edge_attrs_string(edge_label)))
result.append('}')
return '\n'.join(result)
def write_dot(dot, filename, neato_options={}, *args, **kwargs):
program = kwargs.pop('program', 'dot')
f = open(filename, 'w')
f.write(dot.encode('utf-8'))
f.close()
neato_options_str = ' '.join("-G%s=%s" % pair for pair in neato_options.iteritems())
cmd = "%s %s %s -T png -o %s.png" % (program, filename, neato_options_str, filename)
os.system(cmd)
def main():
filename = '/tmp/model_graph.dot'
models = get_all_models()
relns = get_relns(models)
dot = relns_to_dot(models, *relns)
write_dot(dot, filename)
if __name__ == '__main__':
main()
|
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
I love the idea but it doesn't work for me. I get the Django models but not my own. Am I doing something wrong here:
DJANGO_SETTINGS_MODULE=myproject.settings python ../graphdep.py
I will try to track down the problem later - just thought I'd give a heads up now.
#
@aarond10ster: I put graphdep.py into the project (not the parent of the project). Then I did this:
#
To #1, it depends on your setup. It should work like any other python script.
To #3: A few differences. This one: does not display database columns; automatically detects your installed apps; is a stand-alone script; visually distinguishes built-in models from your own; and, in my humble opinion, produces sexier output.
#
It looks like
edge_len
is unused inrelns_to_dot()
Is that intentional?
#
Nope!
#
This thing also has path issues and is not updated to work with model inheritence. get_all_models should use importlib and should check whether obj issubclass of django.db.models.model
#
Please login first before commenting.