CORD-1337 change content_type from ids to strings

Change-Id: Id26e4205c87297a8e173109a57b5578cc254b5e4
diff --git a/xos/core/admin.py b/xos/core/admin.py
index af24612..8752f24 100644
--- a/xos/core/admin.py
+++ b/xos/core/admin.py
@@ -848,8 +848,7 @@
     fieldsets = [
         (None, {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
     # node no longer directly connected to deployment
-    #inlines = [DeploymentPrivilegeInline,NodeInline,TagInline,ImageDeploymentsInline]
-    inlines = [DeploymentPrivilegeInline, TagInline,
+    inlines = [DeploymentPrivilegeInline,
                ImageDeploymentsInline, SiteDeploymentInline]
     list_display = ['backend_status_icon', 'name']
     list_display_links = ('backend_status_icon', 'name', )
@@ -1129,7 +1128,7 @@
                     'login_base', 'site_url', 'enabled')
     list_display_links = ('backend_status_icon', 'name', )
     filter_horizontal = ('deployments',)
-    inlines = [SliceInline, UserInline, TagInline,
+    inlines = [SliceInline, UserInline,
                SitePrivilegeInline, SiteNodeInline]
     admin_inlines = [ControllerSiteInline]
     search_fields = ['name']
@@ -1279,7 +1278,7 @@
                     'serviceClass', 'slice_url', 'max_instances')
     list_display_links = ('backend_status_icon', 'name', )
     normal_inlines = [SlicePrivilegeInline, InstanceInline,
-                      TagInline, SliceNetworkInline]
+                      SliceNetworkInline]
     inlines = normal_inlines
     admin_inlines = [ControllerSliceInline]
     suit_form_includes = (('slice_instance_tab.html', 'bottom', 'instances'),)
@@ -1292,7 +1291,6 @@
                 ('slicenetworks', 'Networks'),
                 ('sliceprivileges', 'Privileges'),
                 ('instances', 'Instances'),
-                ('tags', 'Tags'),
                 ]
 
         request = getattr(_thread_locals, "request", None)
@@ -1380,7 +1378,7 @@
         #    try to make this work post-demo.
         if (obj is not None) and (obj.name == "mysite_vcpe"):
             cord_vcpe_inlines = [SlicePrivilegeInline, CordInstanceInline,
-                                 TagInline, SliceNetworkInline]
+                                 SliceNetworkInline]
 
             inlines = []
             for inline_class in cord_vcpe_inlines:
@@ -1499,16 +1497,16 @@
     list_display_links = ('backend_status_icon', 'name', )
     list_filter = ('site_deployment',)
 
-    inlines = [TagInline, InstanceInline]
+    inlines = [InstanceInline]
     fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name', 'site_deployment'], 'classes':['suit-tab suit-tab-details']}),
                  ('Labels', {'fields': ['nodelabels'], 'classes':['suit-tab suit-tab-labels']})]
     readonly_fields = ('backend_status_text', )
 
     user_readonly_fields = ['name', 'site_deployment']
-    user_readonly_inlines = [TagInline, InstanceInline]
+    user_readonly_inlines = [InstanceInline]
 
     suit_form_tabs = (('details', 'Node Details'), ('instances',
-                                                    'Instances'), ('labels', 'Labels'), ('tags', 'Tags'))
+                                                    'Instances'), ('labels', 'Labels'))
 
     def formfield_for_foreignkey(self, db_field, request, **kwargs):
         if db_field.name == 'site':
@@ -1538,13 +1536,12 @@
         }
         fields = '__all__'
 
-
 class TagAdmin(XOSBaseAdmin):
     list_display = ['backend_status_icon', 'service',
-                    'name', 'value', 'content_type', 'content_object', ]
+                    'name', 'value', 'content_type', 'object_id', ]
     list_display_links = list_display
     user_readonly_fields = ['service', 'name',
-                            'value', 'content_type', 'content_object', ]
+                            'value', 'content_type', 'object_id', ]
     user_readonly_inlines = []
 
 
@@ -1574,9 +1571,9 @@
                           'all_ips_string', 'instance_id', )
 
     suit_form_tabs = (('general', 'Instance Details'), ('ports', 'Ports'),
-                      ('container', 'Container Settings'), ('tags', 'Tags'))
+                      ('container', 'Container Settings'),)
 
-    inlines = [TagInline, InstancePortInline]
+    inlines = [InstancePortInline]
 
     user_readonly_fields = ['slice', 'deployment',
                             'node', 'ip', 'instance_name', 'flavor', 'image']
@@ -1655,40 +1652,6 @@
 
         return field
 
-# class ContainerPortInline(XOSTabularInline):
-#    fields = ['backend_status_icon', 'network', 'container', 'ip', 'mac', 'segmentation_id']
-#    readonly_fields = ("backend_status_icon", "ip", "mac", "segmentation_id")
-#    model = Port
-#    selflink_fieldname = "network"
-#    extra = 0
-#    verbose_name_plural = "Ports"
-#    verbose_name = "Port"
-#    suit_classes = 'suit-tab suit-tab-ports'
-
-# class ContainerAdmin(XOSBaseAdmin):
-#    fieldsets = [
-#        ('Container Details', {'fields': ['backend_status_text', 'slice', 'node', 'docker_image', 'volumes', 'no_sync'], 'classes': ['suit-tab suit-tab-general'], })
-#    ]
-#    readonly_fields = ('backend_status_text', )
-#    list_display = ['backend_status_icon', 'id']
-#    list_display_links = ('backend_status_icon', 'id', )
-#
-#    suit_form_tabs =(('general', 'Container Details'), ('ports', 'Ports'))
-#
-#    inlines = [TagInline, ContainerPortInline]
-#
-#    def formfield_for_foreignkey(self, db_field, request, **kwargs):
-#        if db_field.name == 'slice':
-#            kwargs['queryset'] = Slice.select_by_user(request.user)
-#
-#        return super(ContainerAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
-#
-#    def queryset(self, request):
-#        # admins can see all instances. Users can only see instances of
-#        # the slices they belong to.
-#        return Container.select_by_user(request.user)
-
-
 class UserCreationForm(forms.ModelForm):
     """A form for creating new users. Includes all the required
     fields, plus a repeated password."""
diff --git a/xos/core/models/attic/networkparameter_model.py b/xos/core/models/attic/networkparameter_model.py
index 2bbfbe7..d2e301ba 100644
--- a/xos/core/models/attic/networkparameter_model.py
+++ b/xos/core/models/attic/networkparameter_model.py
@@ -1,5 +1,3 @@
-content_object = GenericForeignKey('content_type', 'object_id')
-
 def __unicode__(self):
     return self.parameter.name
 
diff --git a/xos/core/models/attic/port_top.py b/xos/core/models/attic/port_top.py
index 67dabf0..5fa8ca9 100644
--- a/xos/core/models/attic/port_top.py
+++ b/xos/core/models/attic/port_top.py
@@ -8,15 +8,15 @@
     def get_parameters(self):
         parameter_dict = {}
 
-        instance_type = ContentType.objects.get_for_model(self)
-        for param in NetworkParameter.objects.filter(content_type__pk=instance_type.id, object_id=self.id):
+        instance_type = self.get_content_type_key()
+        for param in NetworkParameter.objects.filter(content_type=instance_type, object_id=self.id):
             parameter_dict[param.parameter.name] = param.value
 
         return parameter_dict
 
     def set_parameter(self, name, value):
-        instance_type = ContentType.objects.get_for_model(self)
-        existing_params = NetworkParameter.objects.filter(parameter__name=name, content_type__pk=instance_type.id, object_id=self.id)
+        instance_type = self.get_content_type_key()
+        existing_params = NetworkParameter.objects.filter(parameter__name=name, content_type=instance_type, object_id=self.id)
         if existing_params:
             p=existing_params[0]
             p.value = value
@@ -27,8 +27,8 @@
             p.save()
 
     def unset_parameter(self, name):
-        instance_type = ContentType.objects.get_for_model(self)
-        existing_params = NetworkParameter.objects.filter(parameter__name=name, content_type__pk=instance_type.id, object_id=self.id)
+        instance_type = self.get_content_type_key()
+        existing_params = NetworkParameter.objects.filter(parameter__name=name, content_type=instance_type, object_id=self.id)
         for p in existing_params:
             p.delete()
 
diff --git a/xos/core/models/attic/tag_model.py b/xos/core/models/attic/tag_model.py
index c6437e8..7872679 100644
--- a/xos/core/models/attic/tag_model.py
+++ b/xos/core/models/attic/tag_model.py
@@ -1,5 +1,3 @@
-content_object = GenericForeignKey('content_type', 'object_id') # Not generated by xproto
-
 def __unicode__(self):
     return self.name
 
@@ -8,7 +6,7 @@
 
 @classmethod
 def select_by_content_object(cls, obj):
-    return cls.objects.filter(content_type=ContentType.objects.get_for_model(obj), object_id=obj.id)
+    return cls.objects.filter(content_type=obj.get_content_type_key(), object_id=obj.id)
 
 @staticmethod
 def select_by_user(user):
diff --git a/xos/core/models/instance.xproto b/xos/core/models/instance.xproto
index b411bd4..bc86813 100644
--- a/xos/core/models/instance.xproto
+++ b/xos/core/models/instance.xproto
@@ -17,5 +17,4 @@
      required string isolation = 14 [default = "vm", choices = "(('vm', 'Virtual Machine'), ('container', 'Container'), ('container_vm', 'Container In VM'))", max_length = 30, blank = False, null = False, db_index = False];
      optional string volumes = 15 [help_text = "Comma-separated list of directories to expose to parent context", null = True, db_index = False, blank = True];
      optional manytoone parent->Instance:instance = 16 [help_text = "Parent Instance for containers nested inside of VMs", null = True, db_index = True, blank = True];
-     required manytomany tags->Tag = 17 [db_index = False, null = False, blank = True];
 }
diff --git a/xos/core/models/networkparameter.xproto b/xos/core/models/networkparameter.xproto
index 81585ee..a162d9a 100644
--- a/xos/core/models/networkparameter.xproto
+++ b/xos/core/models/networkparameter.xproto
@@ -3,6 +3,6 @@
 message NetworkParameter (PlCoreBase){
      required manytoone parameter->NetworkParameterType:networkparameters = 1 [help_text = "The type of the parameter", null = False, db_index = True, blank = False];
      required string value = 2 [help_text = "The value of this parameter", max_length = 1024, null = False, db_index = False, blank = False];
-     required manytoone content_type->ContentType:networkparameter = 3 [db_index = True, null = False, blank = False];
-     required uint32 object_id = 4 [db_index = False, null = False, blank = False];
+     required string content_type = 4 [max_length = 1024, content_type = "stripped", blank = False, help_text = "Content type id linked to this network parameter", null = False, db_index = False];
+     required uint32 object_id = 4 [db_index = False, null = False, blank = False, help_text = "Object linked to this NetworkParameter"];
 }
diff --git a/xos/core/models/node.xproto b/xos/core/models/node.xproto
index 3a88b6a..9000fbb 100644
--- a/xos/core/models/node.xproto
+++ b/xos/core/models/node.xproto
@@ -4,5 +4,4 @@
      required string name = 1 [max_length = 200, content_type = "stripped", blank = False, help_text = "Name of the Node", null = False, db_index = False];
      required manytoone site_deployment->SiteDeployment:nodes = 2 [db_index = True, null = False, blank = False];
      optional manytoone site->Site:nodes = 3 [db_index = True, null = True, blank = True];
-     required manytomany tags->Tag = 4 [db_index = False, null = False, blank = True];
 }
diff --git a/xos/core/models/plcorebase.py b/xos/core/models/plcorebase.py
index dd86bac..a99fd15 100644
--- a/xos/core/models/plcorebase.py
+++ b/xos/core/models/plcorebase.py
@@ -13,6 +13,7 @@
 from journal import journal_object
 from django.db.models.deletion import Collector
 from django.db import router
+from django.contrib.contenttypes.models import ContentType
 
 import redis
 from redis import ConnectionError
@@ -430,9 +431,25 @@
 
         return d
 
+    def get_content_type_key(self):
+        ct = ContentType.objects.get_for_model(self.__class__)
+        return "%s.%s" % (ct.app_label, ct.model)
+
+    @staticmethod
+    def get_content_type_from_key(key):
+        (app_name, model_name) = key.split(".")
+        return ContentType.objects.get_by_natural_key(app_name, model_name)
+
+    @staticmethod
+    def get_content_object(content_type, object_id):
+        ct = PlCoreBase.get_content_type_from_key(content_type)
+        cls = ct.model_class()
+        return cls.objects.get(id=object_id)
+
 class ModelLink:
     def __init__(self,dest,via,into=None):
         self.dest=dest
         self.via=via
         self.into=into
 
+
diff --git a/xos/core/models/role.xproto b/xos/core/models/role.xproto
index 2ebde68..cb36794 100644
--- a/xos/core/models/role.xproto
+++ b/xos/core/models/role.xproto
@@ -4,5 +4,5 @@
      required string role_type = 1 [db_index = False, max_length = 80, null = False, content_type = "stripped", blank = False];
      optional string role = 2 [db_index = False, max_length = 80, null = True, content_type = "stripped", blank = True];
      required string description = 3 [db_index = False, max_length = 120, null = False, content_type = "stripped", blank = False];
-     required manytoone content_type->ContentType:role = 4 [db_index = True, null = False, blank = False];
+     required string content_type = 4 [max_length = 1024, content_type = "stripped", blank = False, help_text = "Content type id linked to this role", null = False, db_index = False];
 }
diff --git a/xos/core/models/site.xproto b/xos/core/models/site.xproto
index fe3f9f5..ce2fbd1 100644
--- a/xos/core/models/site.xproto
+++ b/xos/core/models/site.xproto
@@ -12,5 +12,4 @@
      required bool is_public = 9 [help_text = "Indicates the visibility of this site to other members", default = True, null = False, db_index = False, blank = True];
      required string abbreviated_name = 10 [db_index = False, max_length = 80, null = False, content_type = "stripped", blank = False];
      required manytomany deployments->Deployment/SiteDeployment:sites = 11 [help_text = "Select which sites are allowed to host nodes in this deployment", null = False, db_index = False, blank = True];
-     required manytomany tags->Tag = 12 [db_index = False, null = False, blank = True];
 }
diff --git a/xos/core/models/slice.xproto b/xos/core/models/slice.xproto
index 5b1acaa..fde818f 100644
--- a/xos/core/models/slice.xproto
+++ b/xos/core/models/slice.xproto
@@ -16,5 +16,4 @@
      optional manytoone default_node->Node:slices = 15 [db_index = True, null = True, blank = True];
      optional string mount_data_sets = 16 [default = "GenBank", max_length = 256, content_type = "stripped", blank = True, null = True, db_index = False];
      required string default_isolation = 17 [default = "vm", choices = "(('vm', 'Virtual Machine'), ('container', 'Container'), ('container_vm', 'Container In VM'))", max_length = 30, blank = False, null = False, db_index = False];
-     required manytomany tags->Tag = 18 [db_index = False, null = False, blank = True];
 }
diff --git a/xos/core/models/tag.xproto b/xos/core/models/tag.xproto
index 45662a8..6e5daef 100644
--- a/xos/core/models/tag.xproto
+++ b/xos/core/models/tag.xproto
@@ -4,6 +4,6 @@
      required manytoone service->Service:tags = 1 [help_text = "The Service this Tag is associated with", null = False, db_index = True, blank = False];
      required string name = 2 [help_text = "The name of this tag", max_length = 128, null = False, db_index = True, blank = False];
      required string value = 3 [max_length = 1024, content_type = "stripped", blank = False, help_text = "The value of this tag", null = False, db_index = False];
-     required manytoone content_type->ContentType:tag = 4 [db_index = True, null = False, blank = False];
-     required uint32 object_id = 5 [db_index = False, null = False, blank = False];
+     required string content_type = 4 [max_length = 1024, content_type = "stripped", blank = False, help_text = "Content type id linked to this tag", null = False, db_index = False];
+     required uint32 object_id = 5 [db_index = False, null = False, blank = False, help_text = "Object linked to this tag"];
 }
diff --git a/xos/core/models/user.py b/xos/core/models/user.py
index ff7bece..eb11c18 100644
--- a/xos/core/models/user.py
+++ b/xos/core/models/user.py
@@ -20,6 +20,7 @@
 from django.utils import timezone
 from timezones.fields import TimeZoneField
 from journal import journal_object
+from django.contrib.contenttypes.models import ContentType
 
 import redis
 from redis import ConnectionError
@@ -608,6 +609,21 @@
             for db in self.userdashboardviews.all():
                 db.delete()
 
+    def get_content_type_key(self):
+        ct = ContentType.objects.get_for_model(self.__class__)
+        return "%s.%s" % (ct.app_label, ct.model)
+
+    @staticmethod
+    def get_content_type_from_key(key):
+        (app_name, model_name) = key.split(".")
+        return ContentType.objects.get_by_natural_key(app_name, model_name)
+
+    @staticmethod
+    def get_content_object(content_type, object_id):
+        ct = User.get_content_type_from_key(content_type)
+        cls = ct.model_class()
+        return cls.objects.get(id=object_id)
+
 
 class UserDashboardView(PlCoreBase):
     user = models.ForeignKey(User, related_name='userdashboardviews')
diff --git a/xos/coreapi/apihelper.py b/xos/coreapi/apihelper.py
index 6503f12..875c89f 100644
--- a/xos/coreapi/apihelper.py
+++ b/xos/coreapi/apihelper.py
@@ -192,7 +192,7 @@
         bases = [x for x in bases if issubclass(x, PlCoreBase) or issubclass(x, User)]
         p_obj.class_names = ",".join( [x.__name__ for x in bases] )
 
-        p_obj.self_content_type_id = ContentType.objects.get_for_model(obj).id
+        p_obj.self_content_type_id = obj.get_content_type_key()
 
         return p_obj
 
diff --git a/xos/coreapi/protos/xosoptions.proto b/xos/coreapi/protos/xosoptions.proto
index 40e8017..7602c95 100644
--- a/xos/coreapi/protos/xosoptions.proto
+++ b/xos/coreapi/protos/xosoptions.proto
@@ -25,6 +25,6 @@
 }
 
 extend google.protobuf.MessageOptions {
-  int32 contentTypeId = 1001;
+  string contentTypeId = 1001;
 }
 
diff --git a/xos/tools/apigen/protobuf.template.txt b/xos/tools/apigen/protobuf.template.txt
index 4df40ac..26034e5 100644
--- a/xos/tools/apigen/protobuf.template.txt
+++ b/xos/tools/apigen/protobuf.template.txt
@@ -32,7 +32,7 @@
 {% for object in generator.all() %}
 
 message {{ object.camel() }} {
-    option (contentTypeId) = {{ object.content_type_id }};
+    option (contentTypeId) = "{{ object.app_name }}.{{ object|string() }}";
   {%- for field in object.all_fields %}
     oneof {{ field.name }}_present {
     {%- if (field.get_internal_type() == "CharField") or (field.get_internal_type() == "TextField") or (field.get_internal_type() == "SlugField") %}
@@ -66,7 +66,7 @@
     repeated int32 {{ ref.related_name }}_ids  = {{ loop.index+100 }} [(reverseForeignKey).modelName = "{{ ref.camel() }}"];
   {%- endfor %}
   string class_names = 201;
-  int32 self_content_type_id = 202;
+  string self_content_type_id = 202;
 }
 
 message {{ object.camel() }}s {
diff --git a/xos/tosca/resources/tag.py b/xos/tosca/resources/tag.py
index 955fabf..ab7ec30 100644
--- a/xos/tosca/resources/tag.py
+++ b/xos/tosca/resources/tag.py
@@ -18,7 +18,7 @@
         target_name = self.get_requirement("tosca.relationships.TagsObject", throw_exception=throw_exception)
         if target_name:
             target_model = self.engine.name_to_xos_model(self.user, target_name)
-            args["content_type"] = ContentType.objects.get_for_model(target_model)
+            args["content_type"] = target_model.get_content_type_key()
             args["object_id"] = target_model.id
 
         service_name = self.get_requirement("tosca.relationships.MemberOfService", throw_exception=throw_exception)
diff --git a/xos/xos/xosapi.py b/xos/xos/xosapi.py
index 2ebcd41..ba0281b 100644
--- a/xos/xos/xosapi.py
+++ b/xos/xos/xosapi.py
@@ -788,7 +788,7 @@
             return None
     class Meta:
         model = NetworkParameter
-        fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_need_delete','backend_need_reap','backend_status','deleted','write_protect','lazy_blocked','no_sync','no_policy','value','object_id','parameter','content_type',)
+        fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_need_delete','backend_need_reap','backend_status','deleted','write_protect','lazy_blocked','no_sync','no_policy','value','content_type','object_id','parameter',)
 
 class NetworkParameterIdSerializer(XOSModelSerializer):
     id = IdField()
@@ -804,7 +804,7 @@
             return None
     class Meta:
         model = NetworkParameter
-        fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_need_delete','backend_need_reap','backend_status','deleted','write_protect','lazy_blocked','no_sync','no_policy','value','object_id','parameter','content_type',)
+        fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_need_delete','backend_need_reap','backend_status','deleted','write_protect','lazy_blocked','no_sync','no_policy','value','content_type','object_id','parameter',)
 
 
 
@@ -1006,7 +1006,7 @@
             return None
     class Meta:
         model = Tag
-        fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_need_delete','backend_need_reap','backend_status','deleted','write_protect','lazy_blocked','no_sync','no_policy','name','value','object_id','service','content_type',)
+        fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_need_delete','backend_need_reap','backend_status','deleted','write_protect','lazy_blocked','no_sync','no_policy','name','value','content_type','object_id','service',)
 
 class TagIdSerializer(XOSModelSerializer):
     id = IdField()
@@ -1022,7 +1022,7 @@
             return None
     class Meta:
         model = Tag
-        fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_need_delete','backend_need_reap','backend_status','deleted','write_protect','lazy_blocked','no_sync','no_policy','name','value','object_id','service','content_type',)
+        fields = ('humanReadableName', 'validators', 'id','created','updated','enacted','policed','backend_register','backend_need_delete','backend_need_reap','backend_status','deleted','write_protect','lazy_blocked','no_sync','no_policy','name','value','content_type','object_id','service',)
 
 
 
@@ -3371,7 +3371,7 @@
     serializer_class = NetworkParameterSerializer
     id_serializer_class = NetworkParameterIdSerializer
     filter_backends = (filters.DjangoFilterBackend,)
-    filter_fields = ('id','created','updated','enacted','policed','backend_register','backend_need_delete','backend_need_reap','backend_status','deleted','write_protect','lazy_blocked','no_sync','no_policy','value','object_id','parameter','content_type',)
+    filter_fields = ('id','created','updated','enacted','policed','backend_register','backend_need_delete','backend_need_reap','backend_status','deleted','write_protect','lazy_blocked','no_sync','no_policy','value','content_type','object_id','parameter',)
 
     def get_serializer_class(self):
         no_hyperlinks=False
@@ -3653,7 +3653,7 @@
     serializer_class = TagSerializer
     id_serializer_class = TagIdSerializer
     filter_backends = (filters.DjangoFilterBackend,)
-    filter_fields = ('id','created','updated','enacted','policed','backend_register','backend_need_delete','backend_need_reap','backend_status','deleted','write_protect','lazy_blocked','no_sync','no_policy','name','value','object_id','service','content_type',)
+    filter_fields = ('id','created','updated','enacted','policed','backend_register','backend_need_delete','backend_need_reap','backend_status','deleted','write_protect','lazy_blocked','no_sync','no_policy','name','value','content_type','object_id','service',)
 
     def get_serializer_class(self):
         no_hyperlinks=False
diff --git a/xos/xos_client/xosapi/convenience/port.py b/xos/xos_client/xosapi/convenience/port.py
index e6d56ed..c74a58b 100644
--- a/xos/xos_client/xosapi/convenience/port.py
+++ b/xos/xos_client/xosapi/convenience/port.py
@@ -5,7 +5,7 @@
     def get_parameters(self):
         parameter_dict = {}
 
-        for param in self.stub.NetworkParameter.objects.filter(content_type_id=self.self_content_type_id, object_id=self.id):
+        for param in self.stub.NetworkParameter.objects.filter(content_type=self.self_content_type_id, object_id=self.id):
             parameter_dict[param.parameter.name] = param.value
 
         return parameter_dict
diff --git a/xos/xos_client/xosapi/convenience/tag.py b/xos/xos_client/xosapi/convenience/tag.py
new file mode 100644
index 0000000..19338ce
--- /dev/null
+++ b/xos/xos_client/xosapi/convenience/tag.py
@@ -0,0 +1,7 @@
+from xosapi.orm import ORMWrapper, register_convenience_wrapper
+
+class ORMWrapperTag(ORMWrapper):
+    def get_generic_foreignkeys(self):
+        return [{"name": "content_object", "content_type": "content_type", "id": "object_id"}]
+
+register_convenience_wrapper("Tag", ORMWrapperTag)
diff --git a/xos/xos_client/xosapi/convenience/vsgtenant.py b/xos/xos_client/xosapi/convenience/vsgtenant.py
index d58c757..0d3fa90 100644
--- a/xos/xos_client/xosapi/convenience/vsgtenant.py
+++ b/xos/xos_client/xosapi/convenience/vsgtenant.py
@@ -50,7 +50,7 @@
 
     @property
     def wan_vm_ip(self):
-        tags = self.stub.Tag.objects.filter(name="vm_vrouter_tenant", object_id=self.instance.id, content_type_id=self.instance.self_content_type_id)
+        tags = self.stub.Tag.objects.filter(name="vm_vrouter_tenant", object_id=self.instance.id, content_type=self.instance.self_content_type_id)
         if tags:
             tenant = self.stub.VRouterTenant.objects.get(id=int(tags[0].value))
             return tenant.public_ip
@@ -59,7 +59,7 @@
 
     @property
     def wan_vm_mac(self):
-        tags = self.stub.Tag.objects.filter(name="vm_vrouter_tenant", object_id=self.instance.id, content_type_id=self.instance.self_content_type_id)
+        tags = self.stub.Tag.objects.filter(name="vm_vrouter_tenant", object_id=self.instance.id, content_type=self.instance.self_content_type_id)
         if tags:
             tenant = self.stub.VRouterTenant.objects.get(id=int(tags[0].value))
             return tenant.public_mac
diff --git a/xos/xos_client/xosapi/convenience/vtrtenant.py b/xos/xos_client/xosapi/convenience/vtrtenant.py
new file mode 100644
index 0000000..62856c1
--- /dev/null
+++ b/xos/xos_client/xosapi/convenience/vtrtenant.py
@@ -0,0 +1,7 @@
+from xosapi.orm import ORMWrapper, register_convenience_wrapper
+
+class ORMWrapperVTRTenant(ORMWrapper):
+    def get_generic_foreignkeys(self):
+        return [{"name": "target", "content_type": "target_type", "id": "target_id"}]
+
+register_convenience_wrapper("VTRTenant", ORMWrapperVTRTenant)
diff --git a/xos/xos_client/xosapi/fake_stub.py b/xos/xos_client/xosapi/fake_stub.py
index 6cfce8a..dc56178 100644
--- a/xos/xos_client/xosapi/fake_stub.py
+++ b/xos/xos_client/xosapi/fake_stub.py
@@ -9,13 +9,14 @@
 ContentTypeMap = {}
 
 class FakeObj(object):
-    def __init__(self, defaults={"id": 0}, **kwargs):
+    def __init__(self, fields=[], **kwargs):
         super(FakeObj, self).__setattr__("is_set", {})
         super(FakeObj, self).__setattr__("fields", [])
 
-        for (k,v) in defaults.items():
-            self.fields.append(k)
-            setattr(self, k, v)
+        for f in fields:
+            name = f["name"]
+            self.fields.append(name)
+            setattr(self, name, f["default"])
 
         super(FakeObj, self).__setattr__("is_set", {})
         for (k,v) in kwargs.items():
@@ -48,10 +49,32 @@
             return self.extensions[name]
         return default
 
+class FakeFieldOption(object):
+    def __init__(self, modelName=None):
+        self.modelName = modelName
+
+class FakeField(object):
+    def __init__(self, field):
+        extensions = {}
+
+        fk_model = field.get("fk_model", None)
+        if fk_model:
+            extensions["xos.foreignKey"] = FakeFieldOption(modelName=fk_model)
+
+        fk_reverse = field.get("fk_reverse", None)
+        if fk_reverse:
+            extensions["xos.reverseForeignKey"] = FakeFieldOption(modelName=fk_reverse)
+
+        self.Extensions = FakeExtensionManager(self, extensions)
+
+    def GetOptions(self):
+        return self
+
 class FakeDescriptor(object):
     def __init__(self, objName):
         global ContentTypeIdCounter
         global ContentTypeMap
+        self.objName = objName
         if objName in ContentTypeMap:
             ct = ContentTypeMap[objName]
         else:
@@ -65,21 +88,38 @@
 
     @property
     def fields_by_name(self):
-        # TODO: everything
-        return {}
+        cls = globals()[self.objName]
+        fbn = {}
+        for field in cls.FIELDS:
+            fake_field = FakeField(field)
+            fbn[ field["name"] ] = fake_field
+
+        return fbn
 
 class Slice(FakeObj):
+    FIELDS = ( {"name": "id", "default": 0},
+               {"name": "name", "default": ""},
+               {"name": "site_id", "default": 0, "fk_model": "Site"} )
+
     def __init__(self, **kwargs):
-        defaults = {"id": 0,
-                    "name": ""}
-        return super(Slice, self).__init__(defaults, **kwargs)
+        return super(Slice, self).__init__(self.FIELDS, **kwargs)
 
     DESCRIPTOR = FakeDescriptor("Slice")
 
+class Site(FakeObj):
+    FIELDS = ( {"name": "id", "default": 0},
+               {"name": "name", "default": ""},
+               {"name": "slice_ids", "default": 0, "fk_reverse": "Slice"} )
+
+    def __init__(self, **kwargs):
+        return super(Site, self).__init__(self.FIELDS, **kwargs)
+
+    DESCRIPTOR = FakeDescriptor("Site")
+
 class FakeStub(object):
     def __init__(self):
         self.objs = {}
-        for name in ["Slice"]:
+        for name in ["Slice", "Site"]:
             setattr(self, "Get%s" % name, functools.partial(self.get, name))
             setattr(self, "List%s" % name, functools.partial(self.list, name))
             setattr(self, "Create%s" % name, functools.partial(self.create, name))
@@ -118,7 +158,7 @@
 class FakeSymDb(object):
     def __init__(self):
         self._classes = {}
-        for name in ["Slice"]:
+        for name in ["Slice", "Site"]:
             self._classes["xos.%s" % name] = globals()[name]
 
 
diff --git a/xos/xos_client/xosapi/orm.py b/xos/xos_client/xosapi/orm.py
index a030b6a..35f7a3f 100644
--- a/xos/xos_client/xosapi/orm.py
+++ b/xos/xos_client/xosapi/orm.py
@@ -47,6 +47,12 @@
         """
         super(ORMWrapper, self).__setattr__(name, value)
 
+    def get_generic_foreignkeys(self):
+        """ this is a placeholder until generic foreign key support is added
+            to xproto.
+        """
+        return []
+
     def gen_fkmap(self):
         fkmap = {}
 
@@ -65,6 +71,9 @@
                    if type_name in all_field_names:
                        fkmap[name[:-3]] = {"src_fieldName": name, "ct_fieldName": type_name, "kind": "generic_fk"}
 
+        for gfk in self.get_generic_foreignkeys():
+            fkmap[gfk["name"]] = {"src_fieldName": gfk["id"], "ct_fieldName": gfk["content_type"], "kind": "generic_fk"}
+
         return fkmap
 
     def gen_reverse_fkmap(self):
@@ -495,4 +504,5 @@
 import convenience.user
 import convenience.slice
 import convenience.port
-
+import convenience.tag
+import convenience.vtrtenant