blob: 44eaec4bf583113c363239cbb0938323b07bbb13 [file] [log] [blame]
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -05001#!/usr/bin/python
2
3import os
4import pdb
Sapan Bhatiacdd90b72014-01-28 20:03:13 -05005import copy
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -05006import sys
7import json
8import re
Scott Bakerbe53ac12017-01-04 15:41:10 -08009import jinja2
Scott Baker77f7d6f2015-10-23 12:20:15 -070010from optparse import OptionParser
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -050011
12# Django set up
13
Sapan Bhatia879e4e42016-03-04 22:03:07 +010014import django
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -050015sys.path.append('.')
Sapan Bhatia879e4e42016-03-04 22:03:07 +010016sys.path.append('/opt/xos')
Scott Bakerc63b4f62015-02-16 22:43:01 -080017os.environ.setdefault("DJANGO_SETTINGS_MODULE", "xos.settings")
Sapan Bhatiaeb62ad62014-01-28 14:29:08 -050018from django.db.models.fields.related import ForeignKey, ManyToManyField
Scott Baker87fb3022017-02-09 09:33:59 -080019from django.conf import settings
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -050020
Sapan Bhatia879e4e42016-03-04 22:03:07 +010021django.setup()
22
Scott Baker77f7d6f2015-10-23 12:20:15 -070023options = None
24
Sapan Bhatia958226f2016-02-24 19:07:37 +010025
Sapan Bhatiaeb62ad62014-01-28 14:29:08 -050026def singular(foo, keys):
27 for k in keys:
28 if (foo==k+'es'):
29 return k
30 elif (foo==k+'s'):
31 return k
32 raise Exception('Plural to singular error for %s'%foo)
33
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -050034g = globals()
35
Scott Baker49cf7a52015-10-20 11:13:50 -070036def enum_classes(apps):
Sapan Bhatia958226f2016-02-24 19:07:37 +010037 global app_map
Sapan Bhatia879e4e42016-03-04 22:03:07 +010038 global class_map
Sapan Bhatia958226f2016-02-24 19:07:37 +010039 app_map = {}
Sapan Bhatia879e4e42016-03-04 22:03:07 +010040 class_map = {}
Scott Baker49cf7a52015-10-20 11:13:50 -070041 model_classes = []
42 for app in apps:
Sapan Bhatia958226f2016-02-24 19:07:37 +010043 orig_app=app
Scott Baker49cf7a52015-10-20 11:13:50 -070044 app = app + ".models"
45 models_module = __import__(app)
46 for part in app.split(".")[1:]:
47 if hasattr(models_module, "PlCoreBase"):
48 break
49 models_module = getattr(models_module,part)
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -050050
Sapan Bhatia958226f2016-02-24 19:07:37 +010051 global PlCoreBase
Scott Baker49cf7a52015-10-20 11:13:50 -070052 PlCoreBase = getattr(models_module,"PlCoreBase")
53
54 for classname in dir(models_module):
55 c = getattr(models_module, classname, None)
Scott Baker87fb3022017-02-09 09:33:59 -080056
57 # For services, prevent loading of core models as it causes
58 # duplication.
59 if hasattr(c,"_meta") and hasattr(c._meta, "app_label"):
60 if (c._meta.app_label == "core") and (orig_app!="core"):
61 continue
62
Scott Baker77f7d6f2015-10-23 12:20:15 -070063 if type(c)==type(PlCoreBase) and c.__name__ not in options.blacklist:
Scott Baker49cf7a52015-10-20 11:13:50 -070064 model_classes.append(c)
Sapan Bhatia958226f2016-02-24 19:07:37 +010065 app_map[c.__name__]=orig_app
Sapan Bhatia879e4e42016-03-04 22:03:07 +010066 c.class_name = c.__name__
67 file_name = c.__module__.rsplit('.',1)[1]
68 try:
69 if (file_name not in class_map[orig_app]):
70 class_map[orig_app].append({file_name:[c]})
71 else:
72 class_map[orig_app][file_name].append(c)
73
74 except KeyError:
75 class_map[orig_app] = [{file_name:[c]}]
Sapan Bhatia958226f2016-02-24 19:07:37 +010076
Scott Baker49cf7a52015-10-20 11:13:50 -070077
78 return model_classes
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -050079
80class GenObj(object):
81 def __str__(self):
82 return str(self.model.__name__.lower())
83
84 def __init__(self, m):
85 self.model = m
86 self.props = []
Sapan Bhatia958226f2016-02-24 19:07:37 +010087 self.fields = []
Scott Baker841f6422017-01-04 18:28:55 -080088 self.all_fields = []
Sapan Bhatia958226f2016-02-24 19:07:37 +010089 self.field_dict = []
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -050090 self.refs = []
Scott Baker32fbfcf2017-02-07 17:00:27 -080091 self.reverse_refs = []
Sapan Bhatiabe42fba2014-01-28 23:53:49 -050092 self.plural_name = None
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -050093
94 def plural(self):
Sapan Bhatiabe42fba2014-01-28 23:53:49 -050095 if (self.plural_name):
96 return self.plural_name
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -050097 else:
Sapan Bhatiabe42fba2014-01-28 23:53:49 -050098 name = str(self)
99 if (name.endswith('s')):
100 return name+'es'
101 else:
102 return name+'s'
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -0500103
Scott Baker8ffd7d72014-11-10 15:58:58 -0800104 def singular(self):
105 return str(self)
106
107 def rest_name(self):
108 # These are things that either for historic reasons or due to incorrect naming,
109 # got called something different than the autogen thinks they should be
110 # called.
Tony Mack336e0f92014-11-30 15:53:08 -0500111 REST_FIXUP = {'controllernetworkses': 'controllernetworks',
112 'controllerimageses': 'controllerimages',
113 'controllersliceses': 'controllerslices',
114 'controlleruserses': 'controllerusers',
Scott Baker8ffd7d72014-11-10 15:58:58 -0800115 'sitedeploymentses': 'sitedeployments',
116 'siteroles': 'site_roles',
117 'sliceprivileges': 'slice_privileges',
118 'sliceroles': 'slice_roles',
119 }
120 return REST_FIXUP.get(self.plural(), self.plural())
121
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -0500122 def camel(self):
123 name = str(self.model.__name__)
Sapan Bhatiadf2b49e2014-01-28 19:41:07 -0500124 return name
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -0500125
126class Generator(dict):
Sapan Bhatia879e4e42016-03-04 22:03:07 +0100127 def __init__(self):
128 self.apps = {}
129
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -0500130 def all(self):
131 return self.values()
Scott Baker57d66aa2015-10-23 13:09:49 -0700132
133 def rest_models(self):
134 norest = [x.lower() for x in options.norest]
135 return [v for v in self.values() if not (str(v) in norest)]
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -0500136
137 def regex(self, r):
138 filtered = filter(lambda o:re.match(r,str(o)), self.values())
139 return filtered
140
141 def add_object(self, o):
Sapan Bhatia958226f2016-02-24 19:07:37 +0100142 global app_map
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -0500143 obj = GenObj(o)
144 fields = o._meta.fields
Sapan Bhatia958226f2016-02-24 19:07:37 +0100145 try:
Scott Baker87fb3022017-02-09 09:33:59 -0800146 obj.app = app_map[o.__name__] # full name
147 obj.app_name = app_map[o.__name__].split(".")[-1] # only the last part
Sapan Bhatia958226f2016-02-24 19:07:37 +0100148 except KeyError:
149 print "KeyError: %r"%o.__name__
Sapan Bhatia879e4e42016-03-04 22:03:07 +0100150 obj.class_name = o.class_name
151
152 file_name = o.__module__.rsplit('.',1)[1]
153
154 try:
155 if (file_name not in self.apps[obj.app]):
156 self.apps[obj.app][file_name]=[obj]
157 else:
158 self.apps[obj.app][file_name].append(obj)
159
160 except KeyError:
161 self.apps[obj.app] = {file_name:[obj]}
162
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -0500163 self[str(obj).lower()]=obj
164
165 def compute_links(self):
Sapan Bhatia958226f2016-02-24 19:07:37 +0100166 base_props = [f.name for f in PlCoreBase._meta.fields]
167
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -0500168 for obj in self.values():
Sapan Bhatiab5885402014-01-29 10:32:09 -0500169 #if (str(obj)=='network'):
Sapan Bhatiacdd90b72014-01-28 20:03:13 -0500170 # pdb.set_trace()
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -0500171 fields = obj.model._meta.fields
172 for f in fields:
Sapan Bhatiab5885402014-01-29 10:32:09 -0500173 if (f and f.rel):
174 to_name = str(f.rel.to)
175 else:
176 to_name = None
177
178 if type(f)==ForeignKey and to_name and to_name in self.keys():
179 refobj = self[f.to_name]
180
181 if (str(obj)=='slice' and f.to_name=='networks'):
182 obj.refs.append(refobj)
Sapan Bhatiabe42fba2014-01-28 23:53:49 -0500183 related_name = f.related_query_name()
Sapan Bhatiab5885402014-01-29 10:32:09 -0500184 if (related_name!='+' and related_name.lower()!=str(obj).lower()):
Sapan Bhatiabe42fba2014-01-28 23:53:49 -0500185 cobj = copy.deepcopy(obj)
186 cobj.multi = True
187 cobj.plural_name = related_name
188 refobj.refs.append(cobj)
Scott Bakerf71e3d82015-10-30 09:42:34 -0700189 elif f.name.endswith("_ptr"):
190 # django inherited model, for example HPCService
191 # cause swagger and REST to break
192 pass
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -0500193 else:
Sapan Bhatia958226f2016-02-24 19:07:37 +0100194 f.type = f.__class__.__name__
Sapan Bhatia879e4e42016-03-04 22:03:07 +0100195 if (type(f)==ForeignKey):
196 f.related.model.class_name = f.related.model.__name__
Sapan Bhatia958226f2016-02-24 19:07:37 +0100197 if (f.name not in base_props):
Scott Baker841f6422017-01-04 18:28:55 -0800198 obj.fields.append(f)
199 obj.all_fields.append(f)
Sapan Bhatia958226f2016-02-24 19:07:37 +0100200 obj.props.append(f.name)
Sapan Bhatiab5885402014-01-29 10:32:09 -0500201
Sapan Bhatiaeb62ad62014-01-28 14:29:08 -0500202 m2m = obj.model._meta.many_to_many
203 for f in m2m:
204 try:
205 related_model_name = f.m2m_reverse_field_name()
206 except:
207 related_model_name = f.m2m_db_table().rsplit('_',1)[-1]
208
Sapan Bhatiab5885402014-01-29 10:32:09 -0500209 related_name = f.related_query_name()
Sapan Bhatiaeb62ad62014-01-28 14:29:08 -0500210 if related_model_name in self.keys():
Scott Bakerf6404932014-12-15 16:12:43 -0800211 #print "XXX1", obj, f, related_name, related_model_name
Sapan Bhatiaeb62ad62014-01-28 14:29:08 -0500212 refobj = self[related_model_name]
Sapan Bhatiacdd90b72014-01-28 20:03:13 -0500213 cobj = copy.deepcopy(obj)
214 cobj.multi=True
215 refobj.refs.append(cobj)
Scott Baker1e67bb42014-07-03 17:58:10 -0700216
217 # deal with upgradeFrom_rel_+
218 if (related_name.endswith("+")):
219 continue
220
Scott Baker57d66aa2015-10-23 13:09:49 -0700221 if (related_name!='+') and related_model_name in self: # and related_name.lower()!=str(obj).lower()):
Scott Bakerf6404932014-12-15 16:12:43 -0800222 refobj = self[related_model_name]
Scott Baker57d66aa2015-10-23 13:09:49 -0700223 #print "XXX2", obj, f, related_name, related_model_name, refobj.plural_name
Scott Bakerf6404932014-12-15 16:12:43 -0800224 cobj = copy.deepcopy(refobj)
Sapan Bhatiabe42fba2014-01-28 23:53:49 -0500225 cobj.multi = True
Sapan Bhatiab5885402014-01-29 10:32:09 -0500226
Scott Bakerf6404932014-12-15 16:12:43 -0800227 obj.refs.append(cobj)
Sapan Bhatiab5885402014-01-29 10:32:09 -0500228
Scott Baker32fbfcf2017-02-07 17:00:27 -0800229 for obj in self.values():
230 # generate foreign key reverse references
231 for f in obj.model._meta.related_objects:
232 related_model = getattr(f, "related_model", None)
233 if not f.related_name:
234 continue
235 if "+" in f.related_name:
236 continue
237 if related_model and (related_model.__name__.lower() in self.keys()):
238 cobj = copy.deepcopy(self[related_model.__name__.lower()])
239 cobj.related_name = f.related_name
240 obj.reverse_refs.append(cobj)
241
242
Scott Baker87fb3022017-02-09 09:33:59 -0800243def app_has_models(app):
244 """ check whether 'app' includes XOS models """
Sapan Bhatiabe42fba2014-01-28 23:53:49 -0500245
Scott Baker87fb3022017-02-09 09:33:59 -0800246 app = app + ".models"
247 try:
248 models_module = __import__(app)
249 except ImportError:
250 return False
251 for part in app.split(".")[1:]:
252 if hasattr(models_module, "PlCoreBase"):
253 return True
254 models_module = getattr(models_module,part)
Sapan Bhatiaeb62ad62014-01-28 14:29:08 -0500255
Scott Baker87fb3022017-02-09 09:33:59 -0800256 if hasattr(models_module, "PlCoreBase"):
257 return True
258
259 return False
Scott Baker77f7d6f2015-10-23 12:20:15 -0700260
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -0500261def main():
Scott Baker77f7d6f2015-10-23 12:20:15 -0700262 global options
263 parser = OptionParser(usage="modelgen [options] <template_fn>", )
264
Sapan Bhatia879e4e42016-03-04 22:03:07 +0100265 parser.add_option("-d", "--dict", dest="dict",
266 help="dictionary to replace text in output", metavar="DICT", default=[], action="append")
267
Scott Baker77f7d6f2015-10-23 12:20:15 -0700268 parser.add_option("-a", "--app", dest="apps",
269 help="list of applications to parse", metavar="APP", default=[], action="append")
270 parser.add_option("-b", "--blacklist", dest="blacklist",
271 help="add model name to blacklist", metavar="MODEL", default=["SingletonModel", "PlCoreBase"], action="append")
Scott Baker57d66aa2015-10-23 13:09:49 -0700272 parser.add_option("-n", "--no-rest", dest="norest",
273 help="do not generate rest api for model", metavar="MODEL", default=["SingletonModel", "PlCoreBase"], action="append")
Scott Baker77f7d6f2015-10-23 12:20:15 -0700274
275 (options, args) = parser.parse_args(sys.argv[1:])
276
Scott Baker50f76c92015-12-31 13:24:55 -0800277 template_name = os.path.abspath(args[0])
278
279 # try to make sure we're running from the right place
280 if (not os.path.exists("core")):
281 if (os.path.exists("../core")):
282 os.chdir("..")
283 elif (os.path.exists("../../core")):
284 os.chdir("../..")
285 else:
286 print >> sys.stderr, "Are you sure you're running modelgen from the root of an XOS installation"
287 sys.exit(-1)
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -0500288
Scott Baker87fb3022017-02-09 09:33:59 -0800289 if not options.apps:
290 options.apps = ["core"]
291
292 if options.apps == ["*"]:
293 options.apps = [x for x in settings.INSTALLED_APPS if app_has_models(x)]
294
295 if len(args)!=1:
296 print 'Usage: modelgen [options] <template_fn>'
297 exit(1)
298
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -0500299 generator = Generator()
300
Scott Baker77f7d6f2015-10-23 12:20:15 -0700301 models = enum_classes(options.apps)
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -0500302
303 for m in models:
304 generator.add_object(m)
305
306 generator.compute_links()
Scott Bakerbe53ac12017-01-04 15:41:10 -0800307
308 os_template_loader = jinja2.FileSystemLoader( searchpath=[os.path.split(template_name)[0]])
309 os_template_env = jinja2.Environment(loader=os_template_loader)
310
311 template = os_template_env.get_template(os.path.split(template_name)[1])
312 rendered = template.render({"generator": generator})
313
Sapan Bhatia879e4e42016-03-04 22:03:07 +0100314 lines = rendered.splitlines()
315 current_buffer = []
316 for l in lines:
317 if (l.startswith('+++')):
318 filename = l[4:]
319 fil = open(filename,'w')
320 buf = '\n'.join(current_buffer)
321
322 obuf = buf
323 for d in options.dict:
324 df = open(d).read()
325 d = json.loads(df)
326
327 pattern = re.compile(r'\b(' + '|'.join(d.keys()) + r')\b')
328 obuf = pattern.sub(lambda x: d[x.group()], buf)
329 fil.write(obuf)
330 fil.close()
331
332 print 'Written to file %s'%filename
333 current_buffer = []
334 else:
335 current_buffer.append(l)
336 if (current_buffer):
337 print '\n'.join(current_buffer)
Sapan Bhatia3a45f8b2014-01-14 21:20:16 -0500338
339
340if (__name__=='__main__'):
341 main()