Merge branch 'feature/api-cleanup'
diff --git a/xos/api/examples/misc/get_sshkeys.sh b/xos/api/examples/misc/get_sshkeys.sh
new file mode 100755
index 0000000..0ac14c0
--- /dev/null
+++ b/xos/api/examples/misc/get_sshkeys.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+source ./config.sh
+source ./util.sh
+
+curl -H "Accept: application/json; indent=4" -u $AUTH -X GET $HOST/api/utility/sshkeys/
diff --git a/xos/api/utility/portforwarding.py b/xos/api/utility/portforwarding.py
new file mode 100644
index 0000000..0bb7a93
--- /dev/null
+++ b/xos/api/utility/portforwarding.py
@@ -0,0 +1,49 @@
+from rest_framework.decorators import api_view
+from rest_framework.response import Response
+from rest_framework.reverse import reverse
+from rest_framework import serializers
+from rest_framework import generics
+from rest_framework.views import APIView
+from core.models import *
+from django.forms import widgets
+from django.core.exceptions import PermissionDenied
+from xos.exceptions import XOSNotFound
+from api.xosapi_helpers import PlusModelSerializer, XOSViewSet, ReadOnlyField
+from django.db.models import Q
+
+class PortForwardingSerializer(serializers.Serializer):
+    id = serializers.IntegerField(read_only=True)
+    ip = serializers.CharField(read_only=True)
+    ports = serializers.CharField(read_only=True, source="network.ports")
+    hostname = serializers.CharField(read_only=True, source="instance.node.name")
+
+    class Meta:
+        model = Port
+        fields = ('id', 'ip', 'ports', 'hostname')
+
+class PortForwardingViewSet(XOSViewSet):
+    base_name = "portforwarding"
+    method_name = "portforwarding"
+    method_kind = "viewset"
+    serializer_class = PortForwardingSerializer
+
+    def get_queryset(self):
+        queryset=Port.objects.exclude(Q(network__isnull=True) |
+                                                  Q(instance__isnull=True) |
+                                                  Q(instance__node__isnull=True) |
+                                                  Q(network__ports__isnull=True) | Q(network__ports__exact='') |
+                                                  Q(ip__isnull=True))
+
+        node_name = self.request.query_params.get('node_name', None)
+        if node_name is not None:
+            queryset = queryset.filter(instance__node__name = node_name)
+
+        if "" in [q.ip for q in list(queryset)]:
+            # Q(ip__exact=='') does not work right, so let's filter the hard way
+            queryset = [q for q in list(queryset) if q.ip!='']
+            queryset = [q.id for q in queryset]
+            queryset = Port.objects.filter(pk__in=queryset)
+
+        return queryset
+
+
diff --git a/xos/api/utility/sshkeys.py b/xos/api/utility/sshkeys.py
new file mode 100644
index 0000000..6ddd1ef
--- /dev/null
+++ b/xos/api/utility/sshkeys.py
@@ -0,0 +1,42 @@
+from rest_framework.decorators import api_view
+from rest_framework.response import Response
+from rest_framework.reverse import reverse
+from rest_framework import serializers
+from rest_framework import generics
+from rest_framework.views import APIView
+from core.models import *
+from django.forms import widgets
+from django.core.exceptions import PermissionDenied
+from xos.exceptions import XOSNotFound
+from api.xosapi_helpers import PlusModelSerializer, XOSViewSet, ReadOnlyField
+from django.db.models import Q
+
+class SSHKeysSerializer(PlusModelSerializer):
+    id = serializers.CharField(read_only=True, source="instance_id")
+    public_keys = serializers.ListField(read_only=True, source="get_public_keys")
+    node_name = serializers.CharField(read_only=True, source="node.name")
+
+    class Meta:
+        model = Instance
+        fields = ('id', 'public_keys', 'node_name')
+
+class SSHKeysViewSet(XOSViewSet):
+    base_name = "sshkeys"
+    method_name = "sshkeys"
+    method_kind = "viewset"
+    serializer_class = SSHKeysSerializer
+    read_only = True
+
+    lookup_field = "instance_id"
+    lookup_url_kwarg = "pk"
+
+    def get_queryset(self):
+        queryset = queryset=Instance.objects.exclude(Q(instance_id__isnull=True) | Q(instance_id__exact=''))
+
+        node_name = self.request.query_params.get('node_name', None)
+        if node_name is not None:
+            queryset = queryset.filter(node__name = node_name)
+
+        return queryset
+
+
diff --git a/xos/api/xosapi_helpers.py b/xos/api/xosapi_helpers.py
index 4dccb2a..8c737cb 100644
--- a/xos/api/xosapi_helpers.py
+++ b/xos/api/xosapi_helpers.py
@@ -75,6 +75,7 @@
 
 class XOSViewSet(viewsets.ModelViewSet):
     api_path=""
+    read_only=False
 
     @classmethod
     def get_api_method_path(self):
@@ -85,7 +86,7 @@
 
     @classmethod
     def detail_url(self, pattern, viewdict, name):
-        return url(self.get_api_method_path() + r'(?P<pk>[a-zA-Z0-9\-]+)/' + pattern,
+        return url(self.get_api_method_path() + r'(?P<pk>[a-zA-Z0-9\-_]+)/' + pattern,
                    self.as_view(viewdict),
                    name=self.base_name+"_"+name)
 
@@ -101,8 +102,12 @@
 
         patterns = []
 
-        patterns.append(url(self.get_api_method_path() + '$', self.as_view({'get': 'list', 'post': 'create'}), name=self.base_name+'_list'))
-        patterns.append(url(self.get_api_method_path() + '(?P<pk>[a-zA-Z0-9\-]+)/$', self.as_view({'get': 'retrieve', 'put': 'update', 'post': 'update', 'delete': 'destroy', 'patch': 'partial_update'}), name=self.base_name+'_detail'))
+        if self.read_only:
+            patterns.append(url(self.get_api_method_path() + '$', self.as_view({'get': 'list'}), name=self.base_name+'_list'))
+            patterns.append(url(self.get_api_method_path() + '(?P<pk>[a-zA-Z0-9\-_]+)/$', self.as_view({'get': 'retrieve'}), name=self.base_name+'_detail'))
+        else:
+            patterns.append(url(self.get_api_method_path() + '$', self.as_view({'get': 'list', 'post': 'create'}), name=self.base_name+'_list'))
+            patterns.append(url(self.get_api_method_path() + '(?P<pk>[a-zA-Z0-9\-_]+)/$', self.as_view({'get': 'retrieve', 'put': 'update', 'post': 'update', 'delete': 'destroy', 'patch': 'partial_update'}), name=self.base_name+'_detail'))
 
         return patterns
 
diff --git a/xos/core/xoslib/methods/portforwarding.py b/xos/core/xoslib/methods/portforwarding.py
index 0d24423..c98b1bd 100644
--- a/xos/core/xoslib/methods/portforwarding.py
+++ b/xos/core/xoslib/methods/portforwarding.py
@@ -27,7 +27,7 @@
     def get_queryset(self):
         queryset = queryset=Port.objects.all()
 
-        node_name = self.request.QUERY_PARAMS.get('node_name', None)
+        node_name = self.request.query_params.get('node_name', None)
         if node_name is not None:
             queryset = queryset.filter(instance__node__name = node_name)
 
@@ -51,7 +51,7 @@
     def get_queryset(self):
         queryset = queryset=Port.objects.all()
 
-        node_name = self.request.QUERY_PARAMS.get('node_name', None)
+        node_name = self.request.query_params.get('node_name', None)
         if node_name is not None:
             queryset = queryset.filter(instance__node__name = node_name)
 
diff --git a/xos/core/xoslib/methods/sshkeys.py b/xos/core/xoslib/methods/sshkeys.py
index 6223540..705a968 100644
--- a/xos/core/xoslib/methods/sshkeys.py
+++ b/xos/core/xoslib/methods/sshkeys.py
@@ -26,7 +26,7 @@
     def get_queryset(self):
         queryset = queryset=Instance.objects.all()
 
-        node_name = self.request.QUERY_PARAMS.get('node_name', None)
+        node_name = self.request.query_params.get('node_name', None)
         if node_name is not None:
             queryset = queryset.filter(node__name = node_name)
 
@@ -47,7 +47,7 @@
     def get_queryset(self):
         queryset = queryset=Instance.objects.all()
 
-        node_name = self.request.QUERY_PARAMS.get('node_name', None)
+        node_name = self.request.query_params.get('node_name', None)
         if node_name is not None:
             queryset = queryset.filter(node__name = node_name)