#!/usr/bin/env python """Django model to DOT (Graphviz) converter by Antonio Cavedoni Make sure your DJANGO_SETTINGS_MODULE is set to your project or place this script in the same directory of the project and call the script like this: $ python modelviz.py [-h] [-a] [-d] [-g] [-i ] ... > .dot $ dot .dot -Tpng -o .png options: -h, --help show this help message and exit. -a, --all_applications show models from all applications. -d, --disable_fields don't show the class member fields. -g, --group_models draw an enclosing box around models from the same app. -i, --include_models=User,Person,Car only include selected models in graph. """ __version__ = "0.9" __svnid__ = "$Id$" __license__ = "Python" __author__ = "Antonio Cavedoni " __contributors__ = [ "Stefano J. Attardi ", "limodou ", "Carlo C8E Miron", "Andre Campos ", "Justin Findlay ", "Alexander Houben ", "Bas van Oostveen ", ] import getopt, sys from django.core.management import setup_environ try: import settings except ImportError: pass else: setup_environ(settings) from django.utils.safestring import mark_safe from django.template import Template, Context from django.db import models from django.db.models import get_models from django.db.models.fields.related import \ ForeignKey, OneToOneField, ManyToManyField try: from django.db.models.fields.generic import GenericRelation except ImportError: from django.contrib.contenttypes.generic import GenericRelation head_template = """ digraph name { fontname = "Helvetica" fontsize = 8 node [ fontname = "Helvetica" fontsize = 8 shape = "plaintext" ] edge [ fontname = "Helvetica" fontsize = 8 ] """ body_template = """ {% if use_subgraph %} subgraph {{ cluster_app_name }} { label=<
{{ app_name }}
> color=olivedrab4 style="rounded" {% endif %} {% for model in models %} {{ model.app_name }}_{{ model.name }} [label=< {% if not disable_fields %} {% for field in model.fields %} {% endfor %} {% endif %}
{{ model.name }}{% if model.abstracts %}
<{{ model.abstracts|join:"," }}>{% endif %}
{{ field.name }} {{ field.type }}
>] {% endfor %} {% if use_subgraph %} } {% endif %} """ rel_template = """ {% for model in models %} {% for relation in model.relations %} {% if relation.needs_node %} {{ relation.target_app }}_{{ relation.target }} [label=<
{{ relation.target }}
>] {% endif %} {{ model.app_name }}_{{ model.name }} -> {{ relation.target_app }}_{{ relation.target }} [label="{{ relation.name }}"] {{ relation.arrows }}; {% endfor %} {% endfor %} """ tail_template = """ } """ def generate_dot(app_labels, **kwargs): disable_fields = kwargs.get('disable_fields', False) include_models = kwargs.get('include_models', []) all_applications = kwargs.get('all_applications', False) use_subgraph = kwargs.get('group_models', False) dot = head_template apps = [] if all_applications: apps = models.get_apps() for app_label in app_labels: app = models.get_app(app_label) if not app in apps: apps.append(app) graphs = [] for app in apps: graph = Context({ 'name': '"%s"' % app.__name__, 'app_name': "%s" % '.'.join(app.__name__.split('.')[:-1]), 'cluster_app_name': "cluster_%s" % app.__name__.replace(".", "_"), 'disable_fields': disable_fields, 'use_subgraph': use_subgraph, 'models': [] }) for appmodel in get_models(app): abstracts = [e.__name__ for e in appmodel.__bases__ if hasattr(e, '_meta') and e._meta.abstract] abstract_fields = [] for e in appmodel.__bases__: if hasattr(e, '_meta') and e._meta.abstract: abstract_fields.extend(e._meta.fields) model = { 'app_name': app.__name__.replace(".", "_"), 'name': appmodel.__name__, 'abstracts': abstracts, 'fields': [], 'relations': [] } # consider given model name ? def consider(model_name): return not include_models or model_name in include_models if not consider(appmodel._meta.object_name): continue # model attributes def add_attributes(field): model['fields'].append({ 'name': field.name, 'type': type(field).__name__, 'blank': field.blank, 'abstract': field in abstract_fields, }) for field in appmodel._meta.fields: add_attributes(field) if appmodel._meta.many_to_many: for field in appmodel._meta.many_to_many: add_attributes(field) # relations def add_relation(field, extras=""): _rel = { 'target_app': field.rel.to.__module__.replace('.','_'), 'target': field.rel.to.__name__, 'type': type(field).__name__, 'name': field.name, 'arrows': extras, 'needs_node': True } if _rel not in model['relations'] and consider(_rel['target']): model['relations'].append(_rel) for field in appmodel._meta.fields: if isinstance(field, ForeignKey): add_relation(field) elif isinstance(field, OneToOneField): add_relation(field, '[arrowhead=none arrowtail=none]') if appmodel._meta.many_to_many: for field in appmodel._meta.many_to_many: if isinstance(field, ManyToManyField) and getattr(field, 'creates_table', False): add_relation(field, '[arrowhead=normal arrowtail=normal]') elif isinstance(field, GenericRelation): add_relation(field, mark_safe('[style="dotted"] [arrowhead=normal arrowtail=normal]')) graph['models'].append(model) graphs.append(graph) nodes = [] for graph in graphs: nodes.extend([e['name'] for e in graph['models']]) for graph in graphs: # don't draw duplication nodes because of relations for model in graph['models']: for relation in model['relations']: if relation['target'] in nodes: relation['needs_node'] = False # render templates t = Template(body_template) dot += '\n' + t.render(graph) for graph in graphs: t = Template(rel_template) dot += '\n' + t.render(graph) dot += '\n' + tail_template return dot def main(): try: opts, args = getopt.getopt(sys.argv[1:], "hadgi:", ["help", "all_applications", "disable_fields", "group_models", "include_models="]) except getopt.GetoptError, error: print __doc__ sys.exit(error) kwargs = {} for opt, arg in opts: if opt in ("-h", "--help"): print __doc__ sys.exit() if opt in ("-a", "--all_applications"): kwargs['all_applications'] = True if opt in ("-d", "--disable_fields"): kwargs['disable_fields'] = True if opt in ("-g", "--group_models"): kwargs['group_models'] = True if opt in ("-i", "--include_models"): kwargs['include_models'] = arg.split(',') if not args and not kwargs.get('all_applications', False): print __doc__ sys.exit() print generate_dot(args, **kwargs) if __name__ == "__main__": main()