1ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward#!/usr/bin/env python 2ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward"""Django model to DOT (Graphviz) converter 3ed5d92df701c7896c08de7eb2d8fe61957690f0bshowardby Antonio Cavedoni <antonio@cavedoni.org> 4ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 5ed5d92df701c7896c08de7eb2d8fe61957690f0bshowardMake sure your DJANGO_SETTINGS_MODULE is set to your project or 6ed5d92df701c7896c08de7eb2d8fe61957690f0bshowardplace this script in the same directory of the project and call 7ed5d92df701c7896c08de7eb2d8fe61957690f0bshowardthe script like this: 8ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 9ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward$ python modelviz.py [-h] [-d] <app_label> ... <app_label> > <filename>.dot 10ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward$ dot <filename>.dot -Tpng -o <filename>.png 11ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 12ed5d92df701c7896c08de7eb2d8fe61957690f0bshowardoptions: 13ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward -h, --help 14ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward show this help message and exit. 15ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 16ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward -d, --disable_fields 17ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward don't show the class member fields. 18ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward""" 19ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward__version__ = "0.8" 20ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward__svnid__ = "$Id$" 21ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward__license__ = "Python" 22ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward__author__ = "Antonio Cavedoni <http://cavedoni.com/>" 23ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward__contributors__ = [ 24ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward "Stefano J. Attardi <http://attardi.org/>", 25ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward "limodou <http://www.donews.net/limodou/>", 26ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward "Carlo C8E Miron", 27ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward "Andre Campos <cahenan@gmail.com>", 28ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward "Justin Findlay <jfindlay@gmail.com>", 29ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward ] 30ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 31ed5d92df701c7896c08de7eb2d8fe61957690f0bshowardimport getopt, sys 32ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 33ed5d92df701c7896c08de7eb2d8fe61957690f0bshowardfrom django.core.management import setup_environ 34ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 35ed5d92df701c7896c08de7eb2d8fe61957690f0bshowardtry: 36ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward import settings 37ed5d92df701c7896c08de7eb2d8fe61957690f0bshowardexcept ImportError: 38ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward pass 39ed5d92df701c7896c08de7eb2d8fe61957690f0bshowardelse: 40ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward setup_environ(settings) 41ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 42ed5d92df701c7896c08de7eb2d8fe61957690f0bshowardfrom django.template import Template, Context 43ed5d92df701c7896c08de7eb2d8fe61957690f0bshowardfrom django.db import models 44ed5d92df701c7896c08de7eb2d8fe61957690f0bshowardfrom django.db.models import get_models 45ed5d92df701c7896c08de7eb2d8fe61957690f0bshowardfrom django.db.models.fields.related import \ 46ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward ForeignKey, OneToOneField, ManyToManyField 47ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 48ed5d92df701c7896c08de7eb2d8fe61957690f0bshowardtry: 49ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward from django.db.models.fields.generic import GenericRelation 50ed5d92df701c7896c08de7eb2d8fe61957690f0bshowardexcept ImportError: 51ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward from django.contrib.contenttypes.generic import GenericRelation 52ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 53ed5d92df701c7896c08de7eb2d8fe61957690f0bshowardhead_template = """ 54ed5d92df701c7896c08de7eb2d8fe61957690f0bshowarddigraph name { 55ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward fontname = "Helvetica" 56ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward fontsize = 8 57ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 58ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward node [ 59ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward fontname = "Helvetica" 60ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward fontsize = 8 61ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward shape = "plaintext" 62ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward ] 63ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward edge [ 64ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward fontname = "Helvetica" 65ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward fontsize = 8 66ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward ] 67ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 68ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward""" 69ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 70ed5d92df701c7896c08de7eb2d8fe61957690f0bshowardbody_template = """ 71ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward {% for model in models %} 72ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward {% for relation in model.relations %} 73ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward {{ relation.target }} [label=< 74ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward <TABLE BGCOLOR="palegoldenrod" BORDER="0" CELLBORDER="0" CELLSPACING="0"> 75ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward <TR><TD COLSPAN="2" CELLPADDING="4" ALIGN="CENTER" BGCOLOR="olivedrab4" 76ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward ><FONT FACE="Helvetica Bold" COLOR="white" 77ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward >{{ relation.target }}</FONT></TD></TR> 78ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward </TABLE> 79ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward >] 80ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward {{ model.name }} -> {{ relation.target }} 81ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward [label="{{ relation.name }}"] {{ relation.arrows }}; 82ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward {% endfor %} 83ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward {% endfor %} 84ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 85ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward {% for model in models %} 86ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward {{ model.name }} [label=< 87ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward <TABLE BGCOLOR="palegoldenrod" BORDER="0" CELLBORDER="0" CELLSPACING="0"> 88ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward <TR><TD COLSPAN="2" CELLPADDING="4" ALIGN="CENTER" BGCOLOR="olivedrab4" 89ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward ><FONT FACE="Helvetica Bold" COLOR="white" 90ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward >{{ model.name }}</FONT></TD></TR> 91ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 92ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward {% if not disable_fields %} 93ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward {% for field in model.fields %} 94ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward <TR><TD ALIGN="LEFT" BORDER="0" 95ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward ><FONT {% if field.blank %}COLOR="#7B7B7B" {% endif %}FACE="Helvetica Bold">{{ field.name }}</FONT 96ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward ></TD> 97ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward <TD ALIGN="LEFT" 98ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward ><FONT {% if field.blank %}COLOR="#7B7B7B" {% endif %}FACE="Helvetica Bold">{{ field.type }}</FONT 99ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward ></TD></TR> 100ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward {% endfor %} 101ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward {% endif %} 102ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward </TABLE> 103ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward >] 104ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward {% endfor %} 105ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward""" 106ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 107ed5d92df701c7896c08de7eb2d8fe61957690f0bshowardtail_template = """ 108ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward} 109ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward""" 110ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 111ed5d92df701c7896c08de7eb2d8fe61957690f0bshowarddef generate_dot(app_labels, **kwargs): 112ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward disable_fields = kwargs.get('disable_fields', False) 113ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 114ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward dot = head_template 115ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 116ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward for app_label in app_labels: 117ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward app = models.get_app(app_label) 118ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward graph = Context({ 119ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 'name': '"%s"' % app.__name__, 120ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 'disable_fields': disable_fields, 121ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 'models': [] 122ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward }) 123ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 124ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward for appmodel in get_models(app): 125ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward model = { 126ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 'name': appmodel.__name__, 127ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 'fields': [], 128ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 'relations': [] 129ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward } 130ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 131ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward # model attributes 132ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward def add_attributes(): 133ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward model['fields'].append({ 134ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 'name': field.name, 135ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 'type': type(field).__name__, 136ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 'blank': field.blank 137ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward }) 138ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 139ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward for field in appmodel._meta.fields: 140ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward add_attributes() 141ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 142ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward if appmodel._meta.many_to_many: 143ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward for field in appmodel._meta.many_to_many: 144ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward add_attributes() 145ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 146ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward # relations 147ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward def add_relation(extras=""): 148ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward _rel = { 149ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 'target': field.rel.to.__name__, 150ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 'type': type(field).__name__, 151ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 'name': field.name, 152ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 'arrows': extras 153ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward } 154ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward if _rel not in model['relations']: 155ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward model['relations'].append(_rel) 156ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 157ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward for field in appmodel._meta.fields: 158ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward if isinstance(field, ForeignKey): 159ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward add_relation() 160ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward elif isinstance(field, OneToOneField): 161ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward add_relation("[arrowhead=none arrowtail=none]") 162ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 163ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward if appmodel._meta.many_to_many: 164ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward for field in appmodel._meta.many_to_many: 165ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward if isinstance(field, ManyToManyField): 166ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward add_relation("[arrowhead=normal arrowtail=normal]") 167ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward elif isinstance(field, GenericRelation): 168ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward add_relation( 169ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward '[style="dotted"] [arrowhead=normal arrowtail=normal]') 170ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward graph['models'].append(model) 171ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 172ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward t = Template(body_template) 173ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward dot += '\n' + t.render(graph) 174ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 175ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward dot += '\n' + tail_template 176ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 177ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward return dot 178ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 179ed5d92df701c7896c08de7eb2d8fe61957690f0bshowarddef main(): 180ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward try: 181ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward opts, args = getopt.getopt(sys.argv[1:], "hd", 182ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward ["help", "disable_fields"]) 183ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward except getopt.GetoptError, error: 184ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward print __doc__ 185ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward sys.exit(error) 186ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward else: 187ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward if not args: 188ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward print __doc__ 189ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward sys.exit() 190ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 191ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward kwargs = {} 192ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward for opt, arg in opts: 193ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward if opt in ("-h", "--help"): 194ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward print __doc__ 195ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward sys.exit() 196ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward if opt in ("-d", "--disable_fields"): 197ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward kwargs['disable_fields'] = True 198ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward print generate_dot(args, **kwargs) 199ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward 200ed5d92df701c7896c08de7eb2d8fe61957690f0bshowardif __name__ == "__main__": 201ed5d92df701c7896c08de7eb2d8fe61957690f0bshoward main() 202