My favorites | Sign in
Repository Home Source
Checkout   Browse   Changes   Clones    
 
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
#!/usr/bin/env python
#
# Copyright 2008 the Melange authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Produces a graph of the Melange data model. Prints a DOT (Graphviz)
formatted graph on screen if an output png image file is not specified.

Based on Antonio Cavedoni's Django model to DOT (Graphviz) converter from
django-command-extensions and Tim Ansell's graph.py script.

$ python model_graph.ph [-h] [-n] [-y] [-d] [-g] [-i] [-o output.png]

options:
-h, --help
show this help message and exit.

-n, --no_relations
don't draw relations between models.

-y, --draw_inheritance
also draw inheritance relations.

-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_inherited_properties
include inherited properties in every model.

-o, --output=output.png
specifies a file to output a png image.
"""

__authors__ = [
'"Felix \'sttwister\' Kerekes" <sttwister@gmail.com>',
]

import getopt
import os
import sys
import pygraphviz
from django.core.management import setup_environ
from django.template import Template, Context
from inspect import getmembers, isclass, getmro

# Insert at the begining of sys.path to make sure we use the App Engine SDK
# provided by Melange
sys.path.insert(0, os.path.join("..", "thirdparty", "google_appengine"))

sys.path.insert(0, os.path.join("..", "app"))

os.environ['SERVER_SOFTWARE'] = 'Development'
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
import settings
import google
from google.appengine.ext import db

import soc.models as models
import soc.modules.gsoc.models as gsoc_models
import soc.modules.ghop.models as ghop_models

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=<
<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0">
<TR><TD COLSPAN="2" CELLPADDING="4" ALIGN="CENTER"
><FONT FACE="Helvetica Bold" COLOR="Black" POINT-SIZE="12"
>{{ app_name }}</FONT></TD></TR>
</TABLE>
>
color=olivedrab4
style="rounded"
{% endif %}

{% for model in models %}
{{ model.app_name }}_{{ 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 }}{% if model.abstracts %}<BR/>&lt;<FONT FACE="Helvetica Italic">{{ model.abstracts|join:"," }}</FONT>&gt;{% endif %}</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 {% if field.abstract %}Italic{% else %}Bold{% endif %}">{{ field.name }}</FONT
></TD>
<TD ALIGN="LEFT"
><FONT {% if field.blank %}COLOR="#7B7B7B" {% endif %}FACE="Helvetica {% if field.abstract %}Italic{% else %}Bold{% endif %}">{{ field.type }}</FONT
></TD></TR>
{% endfor %}
{% endif %}
</TABLE>
>]
{% 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=<
<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>
>]
{% endif %}
{{ model.app_name }}_{{ model.name }} -> {{ relation.target_app }}_{{ relation.target }}
[label="{{ relation.name }}"] {{ relation.arrows }};
{% endfor %}
{% endfor %}
"""

tail_template = """
}
"""

def generate_dot(modules, no_relations=False, draw_inheritance=False,
disable_fields=False, group_models=False,
include_inherited_properties=False):
"""Generates a DOT formatted graph of all models specified in the modules
parameter.

Parameters:

modules - a dictionary mapping module names to lists of model objects

all default named parameters corespond to command line options
"""
dot = head_template
graphs = []
for module_name, models in modules.items():
app__name__ = module_name
graph = Context({
'name': '"%s"' % app__name__,
'app_name': "%s" % app__name__,
'cluster_app_name': "cluster_%s" % app__name__.replace(".", "_"),
'disable_fields': disable_fields,
'use_subgraph': group_models,
'models': []
})

abstracts = None
for appmodel in models:
model = {
'app_name': app__name__.replace(".", "_"),
'name': appmodel.kind(),
'abstracts': abstracts,
'fields': [],
'relations': []
}

if not disable_fields:
# model attributes
def add_attributes(name, prop):
model['fields'].append({
'name': name,
'type': type(prop).__name__,
})

for name, prop in appmodel.properties().items():
found = False
if not include_inherited_properties:
for parent in appmodel.__bases__:
if hasattr(parent, name):
found = True
break
if not found:
add_attributes(name, prop)


if not no_relations:
def add_relation(name, prop, extras="", referenced_class=None):
if referenced_class:
target = referenced_class
else:
target = prop.reference_class
_rel = {
'target_app': "_".join(target.__module__.split(".")[:-1]),
'target':target.__name__,
'type': type(prop).__name__,
'name': name,
'arrows': "[arrowhead=open arrowtail=none]",
'needs_node': True
}
if _rel not in model['relations']:
model['relations'].append(_rel)

for name, prop in appmodel.properties().items():
if name == 'scope':
module = appmodel.__module__
path = module.split('.')
logic_module = path[:-2]
logic_module.append('logic')
logic_module.append('models')
logic_module.append(path[-1])
logic_module = '.'.join(logic_module)

try:
logic = __import__(logic_module, globals(), locals(), fromlist=['']).logic
scope_logic = logic.getScopeLogic()
if scope_logic:
scope_logic = scope_logic.logic
scope_model = scope_logic.getModel()
add_relation(name, prop, referenced_class=scope_model)
except ImportError:
pass

if type(prop) == db.ReferenceProperty:
found = False
if not include_inherited_properties:
for parent in appmodel.__bases__:
if hasattr(parent, name):
found = True
break
if not found:
add_relation(name, prop)

if draw_inheritance:
def add_inheritance_relation(name, extras=""):
_rel = {
'target_app': "_".join(parent.__module__.split(".")[:-1]),
'target': parent.__name__,
'arrows': "[arrowhead=empty arrowtail=none]",
'needs_node': True
}
if _rel not in model['relations']:
model['relations'].append(_rel)

for parent in appmodel.__bases__:
if google.appengine.ext.db.Model in getmro(parent):
add_inheritance_relation(parent)

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 get_models_by_module():
"""Return a dictionary mapping module name to a list of models in that module.
"""

modules = {}

packages = ['soc.models']
packages.extend(['soc.modules.%s.models' % module_name
for module_name in settings.MODULES])

packages = [(__import__(module_name, fromlist=['']), module_name)
for module_name in packages]

for package, packagename in packages:
for file in os.listdir(os.path.dirname(package.__file__)):
if not file.endswith(".py"):
continue
if "__init__" in file:
continue

modelname = os.path.basename(file)[:-3]
try:
exec("import %s.%s as model" % (packagename, modelname))

for klassname, klass in getmembers(model, isclass):

# Make sure the class is actually defined in the module
klass_module = '.'.join(klass.__module__.split('.')[:-1])
if klass_module != package.__name__:
continue

# Make sure the object in inherited from db.Model
if not google.appengine.ext.db.Model in getmro(klass):
continue

# We want to group objects by module, so we extract the parent package
klassmodule = ".".join(klass.__module__.split(".")[:-1])

if klassmodule not in modules:
modules[klassmodule] = set()
modules[klassmodule].add(klass)

except ImportError, e:
import traceback
print "Was unable to import %s: %s" % (modelname, e)

return modules

def main():
import google
import google.appengine
try:
opts, args = getopt.getopt(sys.argv[1:], "hnydgio:",
["help", "no_relations", "draw_inheritance", "disable_fields",
"group_models", "include_inherited_properties", "output="])
except getopt.GetoptError, error:
print __doc__
sys.exit(error)

output = None
kwargs = {}
for opt, arg in opts:
if opt in ("-h", "--help"):
print __doc__
sys.exit()
if opt in ("-n", "--no_relations"):
kwargs['no_relations'] = True
if opt in ("-y", "--draw_inheritance"):
kwargs['draw_inheritance'] = 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_inherited_properties"):
kwargs['include_inherited_properties'] = True
if opt in ("-o", "--output"):
output = arg

modules = get_models_by_module()
dot = generate_dot(modules, **kwargs)
if output:
G = pygraphviz.AGraph(string=dot)
G.layout(prog='dot')
G.draw(output)
else:
print dot

if __name__ == '__main__':
main()

Change log

d889057ca69f by Felix Kerekes <sttwister> on Jun 12, 2010   Diff
Modified model_graph.py to only include
modules specified in settings.py
Go to: 
Sign in to write a code review

Older revisions

23f2b740db57 by Felix Kerekes <sttwister> on Jun 12, 2010   Diff
Modified model_graph.py to also draw
scope relations.
6e8410c89d14 by Felix Kerekes <sttwister> on May 18, 2010   Diff
Fixed bug with command line parsing in
model graph script.
6c74e760a2f6 by Felix Kerekes <sttwister> on May 14, 2010   Diff
Added script to generate data model
graphs.
All revisions of this file

File info

Size: 11846 bytes, 383 lines
Powered by Google Project Hosting