| #!/usr/bin/env python |
| """Django model to DOT (Graphviz) converter |
| by Antonio Cavedoni <antonio@cavedoni.org> |
| |
| 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] [-d] <app_label> ... <app_label> > <filename>.dot |
| $ dot <filename>.dot -Tpng -o <filename>.png |
| |
| options: |
| -h, --help |
| show this help message and exit. |
| |
| -d, --disable_fields |
| don't show the class member fields. |
| """ |
| __version__ = "0.8" |
| __svnid__ = "$Id$" |
| __license__ = "Python" |
| __author__ = "Antonio Cavedoni <http://cavedoni.com/>" |
| __contributors__ = [ |
| "Stefano J. Attardi <http://attardi.org/>", |
| "limodou <http://www.donews.net/limodou/>", |
| "Carlo C8E Miron", |
| "Andre Campos <cahenan@gmail.com>", |
| "Justin Findlay <jfindlay@gmail.com>", |
| ] |
| |
| import getopt, sys |
| |
| from django.core.management import setup_environ |
| |
| try: |
| import settings |
| except ImportError: |
| pass |
| else: |
| setup_environ(settings) |
| |
| 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 = """ |
| {% for model in models %} |
| {% for relation in model.relations %} |
| {{ relation.target }} [label=< |
| <TABLE BGCOLOR="palegoldenrod" BORDER="0" CELLBORDER="0" CELLSPACING="0"> |
| <TR><TD COLSPAN="2" CELLPADDING="4" ALIGN="CENTER" BGCOLOR="olivedrab4" |
| ><FONT FACE="Helvetica Bold" COLOR="white" |
| >{{ relation.target }}</FONT></TD></TR> |
| </TABLE> |
| >] |
| {{ model.name }} -> {{ relation.target }} |
| [label="{{ relation.name }}"] {{ relation.arrows }}; |
| {% endfor %} |
| {% endfor %} |
| |
| {% for model in models %} |
| {{ model.name }} [label=< |
| <TABLE BGCOLOR="palegoldenrod" BORDER="0" CELLBORDER="0" CELLSPACING="0"> |
| <TR><TD COLSPAN="2" CELLPADDING="4" ALIGN="CENTER" BGCOLOR="olivedrab4" |
| ><FONT FACE="Helvetica Bold" COLOR="white" |
| >{{ model.name }}</FONT></TD></TR> |
| |
| {% if not disable_fields %} |
| {% for field in model.fields %} |
| <TR><TD ALIGN="LEFT" BORDER="0" |
| ><FONT {% if field.blank %}COLOR="#7B7B7B" {% endif %}FACE="Helvetica Bold">{{ field.name }}</FONT |
| ></TD> |
| <TD ALIGN="LEFT" |
| ><FONT {% if field.blank %}COLOR="#7B7B7B" {% endif %}FACE="Helvetica Bold">{{ field.type }}</FONT |
| ></TD></TR> |
| {% endfor %} |
| {% endif %} |
| </TABLE> |
| >] |
| {% endfor %} |
| """ |
| |
| tail_template = """ |
| } |
| """ |
| |
| def generate_dot(app_labels, **kwargs): |
| disable_fields = kwargs.get('disable_fields', False) |
| |
| dot = head_template |
| |
| for app_label in app_labels: |
| app = models.get_app(app_label) |
| graph = Context({ |
| 'name': '"%s"' % app.__name__, |
| 'disable_fields': disable_fields, |
| 'models': [] |
| }) |
| |
| for appmodel in get_models(app): |
| model = { |
| 'name': appmodel.__name__, |
| 'fields': [], |
| 'relations': [] |
| } |
| |
| # model attributes |
| def add_attributes(): |
| model['fields'].append({ |
| 'name': field.name, |
| 'type': type(field).__name__, |
| 'blank': field.blank |
| }) |
| |
| for field in appmodel._meta.fields: |
| add_attributes() |
| |
| if appmodel._meta.many_to_many: |
| for field in appmodel._meta.many_to_many: |
| add_attributes() |
| |
| # relations |
| def add_relation(extras=""): |
| _rel = { |
| 'target': field.rel.to.__name__, |
| 'type': type(field).__name__, |
| 'name': field.name, |
| 'arrows': extras |
| } |
| if _rel not in model['relations']: |
| model['relations'].append(_rel) |
| |
| for field in appmodel._meta.fields: |
| if isinstance(field, ForeignKey): |
| add_relation() |
| elif isinstance(field, OneToOneField): |
| add_relation("[arrowhead=none arrowtail=none]") |
| |
| if appmodel._meta.many_to_many: |
| for field in appmodel._meta.many_to_many: |
| if isinstance(field, ManyToManyField): |
| add_relation("[arrowhead=normal arrowtail=normal]") |
| elif isinstance(field, GenericRelation): |
| add_relation( |
| '[style="dotted"] [arrowhead=normal arrowtail=normal]') |
| graph['models'].append(model) |
| |
| t = Template(body_template) |
| dot += '\n' + t.render(graph) |
| |
| dot += '\n' + tail_template |
| |
| return dot |
| |
| def main(): |
| try: |
| opts, args = getopt.getopt(sys.argv[1:], "hd", |
| ["help", "disable_fields"]) |
| except getopt.GetoptError, error: |
| print __doc__ |
| sys.exit(error) |
| else: |
| if not args: |
| print __doc__ |
| sys.exit() |
| |
| kwargs = {} |
| for opt, arg in opts: |
| if opt in ("-h", "--help"): |
| print __doc__ |
| sys.exit() |
| if opt in ("-d", "--disable_fields"): |
| kwargs['disable_fields'] = True |
| print generate_dot(args, **kwargs) |
| |
| if __name__ == "__main__": |
| main() |