CORD-762 extend core API to all django apps

Change-Id: Iab9aad98e078685c8e24a4474732115527a0263a
diff --git a/xos/grpc/apihelper.py b/xos/grpc/apihelper.py
index e6d3a40..04f22d2 100644
--- a/xos/grpc/apihelper.py
+++ b/xos/grpc/apihelper.py
@@ -12,6 +12,16 @@
 SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
 
 class XOSAPIHelperMixin(object):
+    def __init__(self):
+        import django.apps
+
+        self.models = {}
+        for model in django.apps.apps.get_models():
+            self.models[model.__name__] = model
+
+    def get_model(self, name):
+        return self.models[name]
+
     def getProtoClass(self, djangoClass):
         pClass = getattr(xos_pb2, djangoClass.__name__)
         return pClass
diff --git a/xos/grpc/protos/Makefile b/xos/grpc/protos/Makefile
index df07cfd..aeee046 100644
--- a/xos/grpc/protos/Makefile
+++ b/xos/grpc/protos/Makefile
@@ -82,7 +82,7 @@
 	    sudo make uninstall
 
 rebuild-protos:
-	cd ../../tools/apigen && python ./modelgen -a core protobuf.template.txt > /opt/xos/grpc/protos/xos.proto  
-	cd ../../tools/apigen && python ./modelgen -a core grpc_api.template.py > /opt/xos/grpc/xos_grpc_api.py  
-	cd ../../tools/apigen && python ./modelgen -a core grpc_list_test.template.py > /opt/xos/grpc/list_test.py
-	cd ../../tools/apigen && python ./modelgen -a core chameleon_list_test.template.sh > /opt/xos/grpc/tests/chameleon_list_test.sh
+	cd ../../tools/apigen && python ./modelgen -a "*" protobuf.template.txt > /opt/xos/grpc/protos/xos.proto  
+	cd ../../tools/apigen && python ./modelgen -a "*" grpc_api.template.py > /opt/xos/grpc/xos_grpc_api.py  
+	cd ../../tools/apigen && python ./modelgen -a "*" grpc_list_test.template.py > /opt/xos/grpc/list_test.py
+	cd ../../tools/apigen && python ./modelgen -a "*" chameleon_list_test.template.sh > /opt/xos/grpc/tests/chameleon_list_test.sh
diff --git a/xos/tools/apigen/chameleon_list_test.template.sh b/xos/tools/apigen/chameleon_list_test.template.sh
index e46503d..c3297f3 100644
--- a/xos/tools/apigen/chameleon_list_test.template.sh
+++ b/xos/tools/apigen/chameleon_list_test.template.sh
@@ -7,7 +7,7 @@
 fi
 
 {% for object in generator.all() %}
-curl -f --silent http://$HOSTNAME:8080/xosapi/v1/{{ object.camel()|lower }}s > /dev/null
+curl -f --silent http://$HOSTNAME:8080/xosapi/v1/{{ object.app_name }}/{{ object.plural() }} > /dev/null
 if [[ $? -ne 0 ]]; then
     echo fail {{ object.camel() }}
 fi
diff --git a/xos/tools/apigen/grpc_api.template.py b/xos/tools/apigen/grpc_api.template.py
index 9e23db5..fccec40 100644
--- a/xos/tools/apigen/grpc_api.template.py
+++ b/xos/tools/apigen/grpc_api.template.py
@@ -4,37 +4,42 @@
 from google.protobuf.empty_pb2 import Empty
 
 from django.contrib.auth import authenticate as django_authenticate
-from core.models import *
 from xos.exceptions import *
 from apihelper import XOSAPIHelperMixin
 
 class XosService(xos_pb2.xosServicer, XOSAPIHelperMixin):
     def __init__(self, thread_pool):
-        self.thread_pool = thread_pool

-

-    def stop(self):

+        self.thread_pool = thread_pool
+        XOSAPIHelperMixin.__init__(self)
+
+    def stop(self):
         pass
 
 {% for object in generator.all() %}
     def List{{ object.camel() }}(self, request, context):
       user=self.authenticate(context)
-      return self.querysetToProto({{ object.camel() }}, {{ object.camel() }}.objects.all())
+      model=self.get_model("{{ object.camel() }}")
+      return self.querysetToProto(model, model.objects.all())
 
     def Get{{ object.camel() }}(self, request, context):
       user=self.authenticate(context)
-      return self.get({{ object.camel() }}, request.id)
+      model=self.get_model("{{ object.camel() }}")
+      return self.get(model, request.id)
 
     def Create{{ object.camel() }}(self, request, context):
       user=self.authenticate(context)
-      return self.create({{ object.camel() }}, user, request)
+      model=self.get_model("{{ object.camel() }}")
+      return self.create(model, user, request)
 
     def Delete{{ object.camel() }}(self, request, context):
       user=self.authenticate(context)
-      return self.delete({{ object.camel() }}, user, request.id)
+      model=self.get_model("{{ object.camel() }}")
+      return self.delete(model, user, request.id)
 
     def Update{{ object.camel() }}(self, request, context):
       user=self.authenticate(context)
-      return self.update({{ object.camel() }}, user, request.id, request)
+      model=self.get_model("{{ object.camel() }}")
+      return self.update(model, user, request.id, request)
 
 {% endfor %}
 
diff --git a/xos/tools/apigen/modelgen b/xos/tools/apigen/modelgen
index 2beb508..44eaec4 100755
--- a/xos/tools/apigen/modelgen
+++ b/xos/tools/apigen/modelgen
@@ -16,6 +16,7 @@
 sys.path.append('/opt/xos')
 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "xos.settings")
 from django.db.models.fields.related import ForeignKey, ManyToManyField
+from django.conf import settings
 
 django.setup()
 
@@ -52,6 +53,13 @@
 
             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 type(c)==type(PlCoreBase) and c.__name__ not in options.blacklist:
                             model_classes.append(c)
                             app_map[c.__name__]=orig_app
@@ -135,7 +143,8 @@
 		obj = GenObj(o)
 		fields = o._meta.fields
                 try:
-                    obj.app = app_map[o.__name__]
+                    obj.app = app_map[o.__name__] # full name
+                    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
@@ -231,8 +240,23 @@
                                 obj.reverse_refs.append(cobj)
 
 
+def app_has_models(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 hasattr(models_module, "PlCoreBase"):
+            return True
+        models_module = getattr(models_module,part)
 
+    if hasattr(models_module, "PlCoreBase"):
+        return True
+
+    return False
 
 def main():
         global options
@@ -250,13 +274,6 @@
 
         (options, args) = parser.parse_args(sys.argv[1:])
 
-        if not options.apps:
-            options.apps = ["core"]
-
-        if len(args)!=1:
-            print 'Usage: modelgen [options] <template_fn>'
-            exit(1)
-
         template_name = os.path.abspath(args[0])
 
         # try to make sure we're running from the right place
@@ -269,6 +286,16 @@
                 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_has_models(x)]
+
+        if len(args)!=1:
+            print 'Usage: modelgen [options] <template_fn>'
+            exit(1)
+
 	generator = Generator()
 
 	models = enum_classes(options.apps)
diff --git a/xos/tools/apigen/protobuf.template.txt b/xos/tools/apigen/protobuf.template.txt
index 1c9ce4b..75d0b3c 100644
--- a/xos/tools/apigen/protobuf.template.txt
+++ b/xos/tools/apigen/protobuf.template.txt
@@ -72,29 +72,29 @@
 {% for object in generator.all() %}
   rpc List{{ object.camel() }}(google.protobuf.Empty) returns ({{ object.camel() }}s) {
         option (google.api.http) = {
-            get: "/xosapi/v1/{{ object.plural() }}"
+            get: "/xosapi/v1/{{ object.app_name }}/{{ object.plural() }}"
         };
   }
   rpc Get{{ object.camel() }}(ID) returns ({{ object.camel() }}) {
         option (google.api.http) = {
-            get: "/xosapi/v1/{{ object.plural() }}/{id}"
+            get: "/xosapi/v1/{{ object.app_name }}/{{ object.plural() }}/{id}"
         };
   }
   rpc Create{{ object.camel() }}({{ object.camel() }}) returns ({{ object.camel() }}) {
         option (google.api.http) = {
-            post: "/xosapi/v1/{{ object.plural() }}"
+            post: "/xosapi/v1/{{ object.app_name }}/{{ object.plural() }}"
             body: "*"
         };
   }
   rpc Update{{ object.camel() }}({{ object.camel() }}) returns ({{ object.camel() }}) {
         option (google.api.http) = {
-            put: "/xosapi/v1/{{ object.plural() }}/{id}"
+            put: "/xosapi/v1/{{ object.app_name }}/{{ object.plural() }}/{id}"
             body: "*"
         };
   }
   rpc Delete{{ object.camel() }}(ID) returns (google.protobuf.Empty) {
         option (google.api.http) = {
-            delete: "/xosapi/v1/{{ object.plural() }}/{id}"
+            delete: "/xosapi/v1/{{ object.app_name }}/{{ object.plural() }}/{id}"
         };
   }
 {% endfor %}