| #!/usr/bin/python |
| |
| import inspect |
| import os |
| import pdb |
| import copy |
| import sys |
| import json |
| import re |
| import jinja2 |
| from optparse import OptionParser |
| import lib |
| |
| # Django set up |
| |
| import django |
| sys.path.append('.') |
| sys.path.append('/opt/xos') |
| os.environ.setdefault("DJANGO_SETTINGS_MODULE", "xos.settings") |
| from django.db.models.fields.related import ForeignKey, ManyToManyField |
| from django.contrib.contenttypes.fields import GenericRelation |
| from django.conf import settings |
| from django.contrib.contenttypes.models import ContentType |
| |
| django.setup() |
| |
| from core.models import PlCoreBase |
| |
| options = None |
| |
| |
| def is_model_class(model): |
| """ Return True if 'model' is something that we're interested in """ |
| if not inspect.isclass(model): |
| return False |
| if model.__name__ in ["PlModelMixIn"]: |
| return False |
| bases = inspect.getmro(model) |
| bases = [x.__name__ for x in bases] |
| if ("PlCoreBase" in bases) or ("PlModelMixIn" in bases): |
| return True |
| |
| return False |
| |
| def bases(model): |
| return ','.join([b.__name__ for b in model.__bases__]) |
| |
| def module_has_models(module): |
| """ return True if 'module' contains any models we're interested in """ |
| for k in dir(module): |
| v=getattr(module,k) |
| if is_model_class(v): |
| return True |
| |
| return False |
| |
| def app_get_models_module(app): |
| """ check whether 'app' includes XOS models """ |
| |
| app = app + ".models" |
| try: |
| models_module = __import__(app) |
| except ImportError: |
| return False |
| |
| for part in app.split(".")[1:]: |
| if module_has_models(models_module): |
| return models_module |
| models_module = getattr(models_module,part) |
| |
| if module_has_models(models_module): |
| return models_module |
| |
| return None |
| |
| |
| def singular(foo, keys): |
| for k in keys: |
| if (foo==k+'es'): |
| return k |
| elif (foo==k+'s'): |
| return k |
| raise Exception('Plural to singular error for %s'%foo) |
| |
| g = globals() |
| |
| def enum_classes(apps): |
| global app_map |
| global class_map |
| app_map = {} |
| class_map = {} |
| model_classes = [] |
| for app in apps: |
| orig_app=app |
| models_module = app_get_models_module(app) |
| |
| for classname in dir(models_module): |
| c = getattr(models_module, classname, None) |
| |
| # For services, prevent loading of core models as it causes |
| # duplication. |
| if hasattr(c,"_meta") and hasattr(c._meta, "app_label"): |
| if (c._meta.app_label == "core") and (orig_app!="core"): |
| continue |
| |
| if is_model_class(c) and c.__name__ not in options.blacklist: |
| model_classes.append(c) |
| app_map[c.__name__]=orig_app |
| c.class_name = c.__name__ |
| file_name = c.__module__.rsplit('.',1)[1] |
| try: |
| if (file_name not in class_map[orig_app]): |
| class_map[orig_app].append({file_name:[c]}) |
| else: |
| class_map[orig_app][file_name].append(c) |
| |
| except KeyError: |
| class_map[orig_app] = [{file_name:[c]}] |
| |
| |
| return model_classes |
| |
| class GenObj(object): |
| def __str__(self): |
| return str(self.model.__name__.lower()) |
| |
| def __init__(self, m): |
| self.model = m |
| self.props = [] |
| self.fields = [] |
| self.all_fields = [] |
| self.field_dict = [] |
| self.refs = [] |
| self.reverse_refs = [] |
| self.plural_name = None |
| self.content_type_id = ContentType.objects.get_for_model(m).id |
| |
| def plural(self): |
| if (self.plural_name): |
| return self.plural_name |
| else: |
| name = str(self) |
| if (name.endswith('s')): |
| return name+'es' |
| else: |
| return name+'s' |
| |
| def singular(self): |
| return str(self) |
| |
| def rest_name(self): |
| # These are things that either for historic reasons or due to incorrect naming, |
| # got called something different than the autogen thinks they should be |
| # called. |
| REST_FIXUP = {'controllernetworkses': 'controllernetworks', |
| 'controllerimageses': 'controllerimages', |
| 'controllersliceses': 'controllerslices', |
| 'controlleruserses': 'controllerusers', |
| 'sitedeploymentses': 'sitedeployments', |
| 'siteroles': 'site_roles', |
| 'sliceprivileges': 'slice_privileges', |
| 'sliceroles': 'slice_roles', |
| } |
| return REST_FIXUP.get(self.plural(), self.plural()) |
| |
| def camel(self): |
| name = str(self.model.__name__) |
| return name |
| |
| class Generator(dict): |
| def __init__(self): |
| self.apps = {} |
| |
| def all(self): |
| return self.values() |
| |
| def rest_models(self): |
| norest = [x.lower() for x in options.norest] |
| return [v for v in self.values() if not (str(v) in norest)] |
| |
| def regex(self, r): |
| filtered = filter(lambda o:re.match(r,str(o)), self.values()) |
| return filtered |
| |
| def add_object(self, o): |
| global app_map |
| obj = GenObj(o) |
| fields = o._meta.get_fields(include_hidden=False) |
| try: |
| obj.app = app_map[o.__name__] # full name |
| if hasattr(o, "_meta") and hasattr(o._meta, "app_label"): |
| obj.app_name = o._meta.app_label |
| else: |
| obj.app_name = app_map[o.__name__].split(".")[-1] # only the last part |
| except KeyError: |
| print "KeyError: %r"%o.__name__ |
| |
| obj.class_name = o.class_name |
| obj.bases = bases(o) |
| |
| file_name = o.__module__.rsplit('.',1)[1] |
| |
| try: |
| if (file_name not in self.apps[obj.app]): |
| self.apps[obj.app][file_name]=[obj] |
| else: |
| self.apps[obj.app][file_name].append(obj) |
| |
| except KeyError: |
| self.apps[obj.app] = {file_name:[obj]} |
| |
| |
| self[str(obj).lower()]=obj |
| |
| def compute_links(self): |
| |
| for obj in self.values(): |
| base_props = [f.name for f in obj.model.__base__._meta.fields] + ['id'] |
| fields = list(obj.model._meta.fields + obj.model._meta.many_to_many) |
| |
| other_fields = obj.model._meta.get_fields() |
| for o in other_fields: |
| if (type(o)==GenericRelation): fields+=[o] |
| |
| |
| for f in fields: |
| if f.name in base_props or type(f).__name__=='ManyToOneRel': continue |
| |
| if (f and hasattr(f, 'rel') and f.rel): |
| to_name = str(f.rel.to) |
| else: |
| to_name = None |
| |
| |
| if f.name.endswith("_ptr"): |
| # django inherited model, for example HPCService |
| # cause swagger and REST to break |
| pass |
| else: |
| f.type = f.__class__.__name__ |
| |
| if (type(f)==ForeignKey): |
| f.related.model.class_name = f.related.model.__name__ |
| elif (type(f)==ManyToManyField or type(f)==GenericRelation): |
| f.related.model.class_name = f.related.model.__name__ |
| try: |
| f.related.model.through = f.related.through.__name__ |
| except AttributeError: |
| pass |
| |
| |
| if (f.name not in base_props): |
| obj.fields.append(f) |
| |
| #if (f.name == 'tags'): pdb.set_trace() |
| |
| obj.all_fields.append(f) |
| obj.props.append(f.name) |
| |
| m2m = obj.model._meta.many_to_many |
| for f in m2m: |
| try: |
| related_model_name = f.m2m_reverse_field_name() |
| except: |
| related_model_name = f.m2m_db_table().rsplit('_',1)[-1] |
| |
| related_name = f.related_query_name() |
| if related_model_name in self.keys(): |
| #print "XXX1", obj, f, related_name, related_model_name |
| refobj = self[related_model_name] |
| cobj = copy.deepcopy(obj) |
| cobj.multi=True |
| refobj.refs.append(cobj) |
| |
| # deal with upgradeFrom_rel_+ |
| if (related_name.endswith("+")): |
| continue |
| |
| if (related_name!='+') and related_model_name in self: # and related_name.lower()!=str(obj).lower()): |
| refobj = self[related_model_name] |
| #print "XXX2", obj, f, related_name, related_model_name, refobj.plural_name |
| cobj = copy.deepcopy(refobj) |
| cobj.multi = True |
| |
| obj.refs.append(cobj) |
| |
| for obj in self.values(): |
| # generate foreign key reverse references |
| for f in obj.model._meta.related_objects: |
| related_model = getattr(f, "related_model", None) |
| if not f.related_name: |
| continue |
| if "+" in f.related_name: |
| continue |
| if related_model and (related_model.__name__.lower() in self.keys()): |
| cobj = copy.deepcopy(self[related_model.__name__.lower()]) |
| cobj.related_name = f.related_name |
| obj.reverse_refs.append(cobj) |
| |
| def main(): |
| global options |
| parser = OptionParser(usage="modelgen [options] <template_fn>", ) |
| |
| parser.add_option("-d", "--dict", dest="dict", |
| help="dictionary to replace text in output", metavar="DICT", default=[], action="append") |
| |
| parser.add_option("-a", "--app", dest="apps", |
| help="list of applications to parse", metavar="APP", default=[], action="append") |
| parser.add_option("-b", "--blacklist", dest="blacklist", |
| help="add model name to blacklist", metavar="MODEL", default=["SingletonModel", "PlCoreBase"], action="append") |
| parser.add_option("-n", "--no-rest", dest="norest", |
| help="do not generate rest api for model", metavar="MODEL", default=["SingletonModel", "PlCoreBase"], action="append") |
| parser.add_option("-l", "--local", dest="local", |
| help="skip base models", metavar="MODEL", default=False, action="store_true") |
| (options, args) = parser.parse_args(sys.argv[1:]) |
| |
| template_name = os.path.abspath(args[0]) |
| |
| # try to make sure we're running from the right place |
| if (not os.path.exists("core")): |
| if (os.path.exists("../core")): |
| os.chdir("..") |
| elif (os.path.exists("../../core")): |
| os.chdir("../..") |
| else: |
| print >> sys.stderr, "Are you sure you're running modelgen from the root of an XOS installation" |
| sys.exit(-1) |
| |
| if not options.apps: |
| options.apps = ["core"] |
| |
| if options.apps == ["*"]: |
| options.apps = [x for x in settings.INSTALLED_APPS if app_get_models_module(x)] |
| |
| if len(args)!=1: |
| print 'Usage: modelgen [options] <template_fn>' |
| exit(1) |
| |
| generator = Generator() |
| |
| models = enum_classes(options.apps) |
| |
| for m in models: |
| generator.add_object(m) |
| |
| generator.compute_links() |
| |
| os_template_loader = jinja2.FileSystemLoader( searchpath=[os.path.split(template_name)[0]]) |
| os_template_env = jinja2.Environment(loader=os_template_loader) |
| |
| for x in dir(lib): |
| if x.startswith('xp'): |
| os_template_env.globals[x] = getattr(lib, x) |
| |
| template = os_template_env.get_template(os.path.split(template_name)[1]) |
| rendered = template.render({"generator": generator}) |
| |
| lines = rendered.splitlines() |
| current_buffer = [] |
| for l in lines: |
| if (l.startswith('+++')): |
| path = l[4:] |
| |
| direc,filename = path.rsplit('/',1) |
| os.system('mkdir -p %s'%direc) |
| |
| fil = open(path,'w') |
| buf = '\n'.join(current_buffer) |
| |
| obuf = buf |
| for d in options.dict: |
| df = open(d).read() |
| d = json.loads(df) |
| |
| pattern = re.compile(r'\b(' + '|'.join(d.keys()) + r')\b') |
| obuf = pattern.sub(lambda x: d[x.group()], buf) |
| |
| fil.write(obuf) |
| fil.close() |
| |
| print 'Written to file %s'%path |
| current_buffer = [] |
| else: |
| current_buffer.append(l) |
| if (current_buffer): |
| print '\n'.join(current_buffer) |
| |
| |
| if (__name__=='__main__'): |
| main() |