28 | | Make sure your DJANGO_SETTINGS_MODULE is set to your project and call |
29 | | the script like this: |
30 | | |
31 | | $ python modelviz.py <app_label> > <filename>.dot |
32 | | |
33 | | Changelog |
34 | | |
35 | | 0.5.1 |
36 | | Got rid of print statements, now the generate_dot() function returns |
37 | | a string with the file contents |
38 | | |
39 | | 0.5 |
40 | | Cleaned up code, now the relationship arrows start from the |
41 | | correct model attribute position |
42 | | |
43 | | 0.4 |
44 | | Fixed OneToOneField support (thanks, limodou) |
45 | | |
46 | | 0.3 |
47 | | Added support for GenericRelation and OneToOneField |
48 | | |
49 | | 0.2 |
50 | | Changed display (shape="record", thanks Malcolm Tredinnick), |
51 | | fixed arrow direction, added display of model attributes |
52 | | (thanks, Russell Keith-Magee) |
53 | | |
54 | | 0.1 |
55 | | First release. |
56 | | """ |
57 | | |
58 | | __version__ = "0.5.1" |
59 | | __svnid__ = "$Id: modelviz.py 4 2006-08-06 19:48:42Z verbosus $" |
60 | | __license__ = "Python" |
61 | | __author__ = "Antonio Cavedoni <http://cavedoni.com/>" |
62 | | __contributors__ = [ |
63 | | "Stefano J. Attardi <http://attardi.org/>", |
64 | | "limodou <http://www.donews.net/limodou/>" |
65 | | ] |
66 | | |
67 | | from django.db import models |
68 | | from django.db.models import get_models |
69 | | from django.db.models.fields.related import \ |
70 | | ForeignKey, OneToOneField, ManyToManyField |
71 | | from django.db.models.fields.generic import GenericRelation |
72 | | |
73 | | def generate_dot(app_label): |
74 | | app = models.get_app(app_label) |
75 | | |
76 | | graph = [] |
77 | | graph.append("digraph %s {" % ('"' + app.__name__ + '"')) |
78 | | graph.append(""" fontname = "Helvetica" |
79 | | fontsize = 8 |
80 | | |
81 | | node [ |
82 | | fontname = "Helvetica" |
83 | | fontsize = 8 |
84 | | shape = "record" |
85 | | ] |
86 | | edge [ |
87 | | fontname = "Helvetica" |
88 | | fontsize = 8 |
89 | | ] |
90 | | """) |
91 | | for o in get_models(app): |
92 | | graph.append(""" subgraph cluster_%(model)s { |
93 | | shape = "record"; |
94 | | label = "%(model)s"; |
95 | | fontname = "Helvetica Bold"; |
96 | | fontsize = 10; |
97 | | labelfontcolor = "black"; |
98 | | %(model)s [label = "{""" % {'model': o.__name__}) |
99 | | |
100 | | # model attributes |
101 | | def add_attributes(): |
102 | | graph.append("<%(model)s_%(field)s>%(field)s : %(related)s|" % \ |
103 | | {'model': o.__name__, 'field': field.name, |
104 | | 'related': type(field).__name__}) |
105 | | |
106 | | for field in o._meta.fields: |
107 | | add_attributes() |
108 | | |
109 | | if o._meta.many_to_many: |
110 | | for field in o._meta.many_to_many: |
111 | | add_attributes() |
112 | | |
113 | | graph.append(""" }"] [color="white" shape="record"];\n }\n""") |
114 | | |
115 | | # relations |
116 | | rel = [] |
117 | | def add_relation(extras=""): |
118 | | _rel = """ %(model)s:%(model)s_%(field)s -> %(related)s |
119 | | [label="%(relationship)s"] %(extras)s;\n""" % { |
120 | | 'model': o.__name__, 'field': field.name, |
121 | | 'related': field.rel.to.__name__, |
122 | | 'relationship': type(field).__name__, |
123 | | 'extras': extras} |
124 | | if _rel not in rel: |
125 | | rel.append(_rel) |
126 | | |
127 | | for field in o._meta.fields: |
128 | | if isinstance(field, ForeignKey): |
129 | | add_relation() |
130 | | elif isinstance(field, OneToOneField): |
131 | | add_relation("[arrowhead=none arrowtail=none]") |
132 | | |
133 | | if o._meta.many_to_many: |
134 | | for field in o._meta.many_to_many: |
135 | | if isinstance(field, ManyToManyField): |
136 | | add_relation("[arrowhead=normal arrowtail=normal]") |
137 | | elif isinstance(field, GenericRelation): |
138 | | add_relation( |
139 | | '[style="dotted"] [arrowhead=normal arrowtail=normal]') |
140 | | graph.append("".join(rel)) |
141 | | |
142 | | graph.append('}') |
143 | | return "".join(graph) |
144 | | |
145 | | if __name__ == "__main__": |
146 | | import sys |
147 | | app_label = sys.argv[1] |
148 | | print generate_dot(app_label) |
149 | | }}} |
150 | | |
151 | | = Examples = |
152 | | |
153 | | Camera |
| 13 | === Camera website (yet unpublished) === |
167 | | = Feedback = |
168 | | * favo: I have a large app(some model has lots fields). The generated png file is too large. Maybe this tools could work in a concision mode which only contain related field and omit other fields of a model. |
169 | | * elvstone: How did you manage to get the edges to only reach the edge of the subgraphs? I get |
170 | | {{{ |
171 | | #!html |
172 | | <a href="http://bob.dose.se/watchtower.png">this</a> |
173 | | }}} |
174 | | result, which isn't very nice. I've digged through the GraphViz docs/mailing lists without luck :( |
| 27 | === Other examples === |
| 28 | |
| 29 | * [http://zyons.com/ Zyons project] |
| 30 | * Graphviz DOT files for the Zyons project: [http://cavedoni.com/2006/08/zyons-comment.dot comment] [http://cavedoni.com/2006/08/zyons-event.dot event] [http://cavedoni.com/2006/08/zyons-forum.dot forum] [http://cavedoni.com/2006/08/zyons-openid.dot openid] [http://cavedoni.com/2006/08/zyons-prefs.dot prefs] [http://cavedoni.com/2006/08/zyons-sessions.dot sessions] [http://cavedoni.com/2006/08/zyons-tag.dot tag] |
| 31 | * Resulting graphs in PDF: [http://cavedoni.com/2006/08/zyons-comment.pdf comment] [http://cavedoni.com/2006/08/zyons-event.pdf event] [http://cavedoni.com/2006/08/zyons-forum.pdf forum] [http://cavedoni.com/2006/08/zyons-openid.pdf openid] [http://cavedoni.com/2006/08/zyons-prefs.pdf prefs] [http://cavedoni.com/2006/08/zyons-sessions.pdf sessions] [http://cavedoni.com/2006/08/zyons-tag.pdf tag] |
| 32 | |
| 33 | == Other References == |
| 34 | |
| 35 | You might also be interested in [http://www.exit66.com/diagram.zip this Django app by Andrew Barilla ] from which I borrowed some ideas, that displays the graphviz results directly from the web. |
| 36 | |
| 37 | == Feedback == |
| 38 | |
| 39 | Please direct all feedback to [mailto:antonio@cavedoni.org Antonio Cavedoni]. |