Merge branch 'master' of ssh://git.planet-lab.org/git/plstackapi
diff --git a/planetstack/core/admin.py b/planetstack/core/admin.py
index 60a589c..93c14ee 100644
--- a/planetstack/core/admin.py
+++ b/planetstack/core/admin.py
@@ -39,12 +39,62 @@
     exclude = ['enacted']
     extra = 1
 
+class NetworkLookerUpper:
+    """ This is a callable that looks up a network name in a sliver and returns

+        the ip address for that network.

+    """

+

+    def __init__(self, name):

+        self.short_description = name

+        self.__name__ = name

+        self.network_name = name

+

+    def __call__(self, obj):

+        if obj is not None:

+            for nbs in obj.networksliver_set.all():

+                if (nbs.network.name == self.network_name):

+                    return nbs.ip

+        return ""
+
+    def __str__(self):
+        return self.network_name
+
 class SliverInline(PlStackTabularInline):
     model = Sliver
     fields = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']
     extra = 0
-    #readonly_fields = ['ip', 'instance_name', 'image']
     readonly_fields = ['ip', 'instance_name']
+
+
+    def _declared_fieldsets(self):
+        # Return None so django will call get_fieldsets and we can insert our
+        # dynamic fields
+        return None
+
+    def get_readonly_fields(self, request, obj=None):
+        readonly_fields = super(SliverInline, self).get_readonly_fields(request, obj)
+
+        # Lookup the networks that are bound to the slivers, and add those
+        # network names to the list of readonly fields.
+
+        for sliver in obj.slivers.all():
+            for nbs in sliver.networksliver_set.all():
+                if nbs.ip:
+                    network_name = nbs.network.name
+                    if network_name not in [str(x) for x in readonly_fields]:
+                        readonly_fields.append(NetworkLookerUpper(network_name))
+
+        return readonly_fields
+
+    def get_fieldsets(self, request, obj=None):
+        form = self.get_formset(request, obj).form
+        # fields = the read/write files + the read-only fields
+        fields = self.fields
+        for fieldName in self.get_readonly_fields(request,obj):
+            if not fieldName in fields:
+                fields.append(fieldName)
+
+        return [(None, {'fields': fields})]
     
 
 class SiteInline(PlStackTabularInline):
@@ -121,6 +171,12 @@
 
         return super(SliceMembershipInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
 
+class SliceNetworkInline(PlStackTabularInline):
+    model = Network.slices.through
+    extra = 0
+    verbose_name = "Network Connection"
+    verbose_name_plural = "Network Connections"
+
 class SliceTagInline(PlStackTabularInline):
     model = SliceTag
     extra = 0
@@ -271,7 +327,7 @@
 class SliceAdmin(PlanetStackBaseAdmin):
     fields = ['name', 'site', 'serviceClass', 'description', 'slice_url']
     list_display = ('name', 'site','serviceClass', 'slice_url')
-    inlines = [SliverInline, SliceMembershipInline, TagInline, SliceTagInline]
+    inlines = [SliverInline, SliceMembershipInline, TagInline, SliceTagInline, SliceNetworkInline]
 
     def formfield_for_foreignkey(self, db_field, request, **kwargs):
         if db_field.name == 'site':
@@ -402,9 +458,9 @@
 class SliverAdmin(PlanetStackBaseAdmin):
     form = SliverForm
     fieldsets = [
-        ('Sliver', {'fields': ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'key', 'node', 'deploymentNetwork']})
+        ('Sliver', {'fields': ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']})
     ]
-    list_display = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'key', 'node', 'deploymentNetwork']
+    list_display = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']
     inlines = [TagInline]
 
     def formfield_for_foreignkey(self, db_field, request, **kwargs):
@@ -549,10 +605,12 @@
         return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
 
 class ServiceResourceInline(admin.TabularInline):
+    exclude = ['enacted']
     model = ServiceResource
     extra = 0
 
 class ServiceClassAdmin(admin.ModelAdmin):
+    exclude = ['enacted']
     list_display = ('name', 'commitment', 'membershipFee')
     inlines = [ServiceResourceInline]
 
@@ -677,6 +735,53 @@
         else:
             return []
 
+class NetworkParameterTypeAdmin(admin.ModelAdmin):
+    exclude = ['enacted']
+    list_display = ("name", )
+
+class RouterAdmin(admin.ModelAdmin):
+    exclude = ['enacted']
+    list_display = ("name", )
+
+class RouterInline(admin.TabularInline):
+    # exclude = ['enacted']
+    model = Router.networks.through
+    extra = 0
+    verbose_name_plural = "Routers"
+    verbose_name = "Router"
+
+class NetworkParameterInline(generic.GenericTabularInline):
+    exclude = ['enacted']
+    model = NetworkParameter
+    extra = 1
+    verbose_name_plural = "Parameters"
+    verbose_name = "Parameter"
+
+class NetworkSliversInline(admin.TabularInline):
+    exclude = ['enacted']
+    readonly_fields = ("ip", )
+    model = NetworkSliver
+    extra = 0
+    verbose_name_plural = "Slivers"
+    verbose_name = "Sliver"
+
+class NetworkSlicesInline(admin.TabularInline):
+    exclude = ['enacted']
+    model = NetworkSlice
+    extra = 0
+    verbose_name_plural = "Slices"
+    verbose_name = "Slice"
+
+class NetworkAdmin(admin.ModelAdmin):
+    exclude = ['enacted']
+    list_display = ("name", "subnet", "ports", "labels")
+    readonly_fields = ("subnet", )
+    inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
+
+class NetworkTemplateAdmin(admin.ModelAdmin):
+    exclude = ['enacted']
+    list_display = ("name", "guaranteedBandwidth", "visibility")
+
 # register a signal that caches the user's credentials when they log in
 def cache_credentials(sender, user, request, **kwds):
     auth = {'username': request.POST['username'],
@@ -706,6 +811,10 @@
 admin.site.register(Project, ProjectAdmin)
 admin.site.register(ServiceClass, ServiceClassAdmin)
 admin.site.register(Reservation, ReservationAdmin)
+admin.site.register(Network, NetworkAdmin)
+admin.site.register(Router, RouterAdmin)
+admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
+admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
 
 if showAll:
     admin.site.register(Tag, TagAdmin)
diff --git a/planetstack/core/api_root.py b/planetstack/core/api_root.py
index 61e76da..4ac267a 100644
--- a/planetstack/core/api_root.py
+++ b/planetstack/core/api_root.py
@@ -5,14 +5,17 @@
 @api_view(['GET'])
 def api_root(request, format=None):
     return Response({
-        'roles': reverse('role-list', request=request, format=format),
-        'users': reverse('user-list', request=request, format=format),
-        'keys': reverse('key-list', request=request, format=format),
-        #'nodes': reverse('node-list', request=request, format=format),
-        'sites': reverse('site-list', request=request, format=format),
-        'deploymentNetworks': reverse('deploymentnetwork-list', request=request, format=format),
-        'slices': reverse('slice-list', request=request, format=format),
-        'subnets': reverse('subnet-list', request=request, format=format),
-        'slivers': reverse('sliver-list', request=request, format=format),
+        'deployments': reverse('deployment-list', request=request, format=format),
         'images': reverse('image-list', request=request, format=format),
+        'nodes': reverse('node-list', request=request, format=format),
+        'projects': reverse('project-list', request=request, format=format),
+        'reservations': reverse('reservation-list', request=request, format=format),
+        'roles': reverse('role-list', request=request, format=format),
+        'serviceclasses': reverse('serviceclass-list', request=request, format=format),
+        'serviceresources': reverse('serviceresource-list', request=request, format=format),
+        'sites': reverse('site-list', request=request, format=format),
+        'slices': reverse('slice-list', request=request, format=format),
+        'slivers': reverse('sliver-list', request=request, format=format),
+        'tags': reverse('tag-list', request=request, format=format),
+        'users': reverse('user-list', request=request, format=format),
     })
diff --git a/planetstack/core/models/__init__.py b/planetstack/core/models/__init__.py
index 2280822..9b19821 100644
--- a/planetstack/core/models/__init__.py
+++ b/planetstack/core/models/__init__.py
@@ -16,3 +16,4 @@
 from .sliver import Sliver
 from .reservation import ReservedResource
 from .reservation import Reservation
+from .network import Network, NetworkParameterType, NetworkParameter, NetworkSliver, NetworkTemplate, Router, NetworkSlice
diff --git a/planetstack/core/models/network.py b/planetstack/core/models/network.py
new file mode 100644
index 0000000..55711a4
--- /dev/null
+++ b/planetstack/core/models/network.py
@@ -0,0 +1,122 @@
+import os
+import socket
+from django.db import models
+from core.models import PlCoreBase, Site, Slice, Sliver
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.contenttypes import generic
+
+# If true, then IP addresses will be allocated by the model. If false, then
+# we will assume the observer handles it.
+NO_OBSERVER=False
+
+class NetworkTemplate(PlCoreBase):
+    VISIBILITY_CHOICES = (('public', 'public'), ('private', 'private'))
+    TRANSLATION_CHOICES = (('none', 'none'), ('NAT', 'NAT'))
+
+    name = models.CharField(max_length=32)
+    description = models.CharField(max_length=1024, blank=True, null=True)
+    guaranteedBandwidth = models.IntegerField(default=0)
+    visibility = models.CharField(max_length=30, choices=VISIBILITY_CHOICES, default="private")
+    translation = models.CharField(max_length=30, choices=TRANSLATION_CHOICES, default="none")
+    sharedNetworkName = models.CharField(max_length=30, blank=True, null=True)
+    sharedNetworkId = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum network")
+
+    def __unicode__(self):  return u'%s' % (self.name)
+
+class Network(PlCoreBase):
+    name = models.CharField(max_length=32)
+    template = models.ForeignKey(NetworkTemplate)
+    subnet = models.CharField(max_length=32, blank=True)
+    ports = models.CharField(max_length=1024, blank=True, null=True)
+    labels = models.CharField(max_length=1024, blank=True, null=True)
+    owner = models.ForeignKey(Slice, related_name="ownedNetworks")
+
+    guaranteedBandwidth = models.IntegerField(default=0)
+    permitAllSlices = models.BooleanField(default=False)
+    permittedSlices = models.ManyToManyField(Slice, blank=True, related_name="availableNetworks")
+    slices = models.ManyToManyField(Slice, blank=True, related_name="networks", through="NetworkSlice")
+    slivers = models.ManyToManyField(Sliver, blank=True, related_name="networks", through="NetworkSliver")
+
+    # for observer/manager
+    network_id = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum network")
+    router_id = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum router id")
+    subnet_id = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum subnet id")
+
+    def __unicode__(self):  return u'%s' % (self.name)
+
+    def save(self, *args, **kwds):
+        if (not self.subnet) and (NO_OBSERVER):
+            from util.network_subnet_allocator import find_unused_subnet
+            self.subnet = find_unused_subnet(existing_subnets=[x.subnet for x in Network.objects.all()])
+        super(Network, self).save(*args, **kwds)
+
+class NetworkSlice(PlCoreBase):
+    # This object exists solely so we can implement the permission check when
+    # adding slices to networks. It adds no additional fields to the relation.
+
+    network = models.ForeignKey(Network)
+    slice = models.ForeignKey(Slice)
+
+    def save(self, *args, **kwds):
+        slice = self.slice
+        if (slice not in self.network.permittedSlices.all()) and (slice != self.network.owner) and (not self.network.permitAllSlices):
+            # to add a sliver to the network, then one of the following must be true:
+            #   1) sliver's slice is in network's permittedSlices list,
+            #   2) sliver's slice is network's owner, or
+            #   3) network's permitAllSlices is true
+            raise ValueError("Slice %s is not allowed to connect to network %s" % (str(slice), str(self.network)))
+
+        super(NetworkSlice, self).save(*args, **kwds)
+
+    def __unicode__(self):  return u'%s-%s' % (self.network.name, self.slice.name)
+
+class NetworkSliver(PlCoreBase):
+    network = models.ForeignKey(Network)
+    sliver = models.ForeignKey(Sliver)
+    ip = models.GenericIPAddressField(help_text="Sliver ip address", blank=True, null=True)
+    port_id = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum port id")
+
+    def save(self, *args, **kwds):
+        slice = self.sliver.slice
+        if (slice not in self.network.permittedSlices.all()) and (slice != self.network.owner) and (not self.network.permitAllSlices):
+            # to add a sliver to the network, then one of the following must be true:
+            #   1) sliver's slice is in network's permittedSlices list,
+            #   2) sliver's slice is network's owner, or
+            #   3) network's permitAllSlices is true
+            raise ValueError("Slice %s is not allowed to connect to network %s" % (str(slice), str(self.network)))
+
+        if (not self.ip) and (NO_OBSERVER):
+            from util.network_subnet_allocator import find_unused_address
+            self.ip = find_unused_address(self.network.subnet,
+                                          [x.ip for x in self.network.networksliver_set.all()])
+        super(NetworkSliver, self).save(*args, **kwds)
+
+    def __unicode__(self):  return u'%s-%s' % (self.network.name, self.sliver.instance_name)
+
+class Router(PlCoreBase):
+    name = models.CharField(max_length=32)
+    owner = models.ForeignKey(Slice, related_name="routers")
+    permittedNetworks = models.ManyToManyField(Network, blank=True, related_name="availableRouters")
+    networks = models.ManyToManyField(Network, blank=True, related_name="routers")
+
+    def __unicode__(self):  return u'%s' % (self.name)
+
+class NetworkParameterType(PlCoreBase):
+    name = models.SlugField(help_text="The name of this parameter", max_length=128)
+    description = models.CharField(max_length=1024)
+
+    def __unicode__(self):  return u'%s' % (self.name)
+
+class NetworkParameter(PlCoreBase):
+    parameter = models.ForeignKey(NetworkParameterType, related_name="parameters", help_text="The type of the parameter")
+    value = models.CharField(help_text="The value of this parameter", max_length=1024)
+
+    # The required fields to do a ObjectType lookup, and object_id assignment
+    content_type = models.ForeignKey(ContentType)
+    object_id = models.PositiveIntegerField()
+    content_object = generic.GenericForeignKey('content_type', 'object_id')
+
+    def __unicode__(self):
+        return self.parameter.name
+
+
diff --git a/planetstack/core/models/plcorebase.py b/planetstack/core/models/plcorebase.py
index 709fdc6..30d4df3 100644
--- a/planetstack/core/models/plcorebase.py
+++ b/planetstack/core/models/plcorebase.py
@@ -1,6 +1,8 @@
 import os
 from django.db import models
 from django.forms.models import model_to_dict
+from openstack.event_manager import EventSender
+
 
 class PlCoreBase(models.Model):
 
@@ -36,6 +38,10 @@
 
     def save(self, *args, **kwargs):
         super(PlCoreBase, self).save(*args, **kwargs)
+        
+        # Tell the observer that the source database has been updated
+        EventSender().fire()
+
         self.__initial = self._dict
 
     @property
diff --git a/planetstack/core/models/role.py b/planetstack/core/models/role.py
index f6c2f2c..fd29848 100644
--- a/planetstack/core/models/role.py
+++ b/planetstack/core/models/role.py
@@ -5,8 +5,8 @@
 
 class Role(PlCoreBase):
 
-    #ROLE_CHOICES = (('admin', 'Admin'), ('pi', 'Principle Investigator'), ('user','User'))
-    role = models.CharField(null=True, blank=True,max_length=256, unique=True)
+    ROLE_CHOICES = (('admin', 'Admin'), ('pi', 'Principle Investigator'), ('tech', 'Technician'), ('user','User'))
+    role = models.CharField(null=True, blank=True,max_length=256, unique=True, choices=ROLE_CHOICES)
     role_type = models.CharField(max_length=80, unique=True)
 
     def __unicode__(self):  return u'%s' % (self.role_type)
diff --git a/planetstack/core/serializers.py b/planetstack/core/serializers.py
index 00d7160..94f5c3c 100644
--- a/planetstack/core/serializers.py
+++ b/planetstack/core/serializers.py
@@ -9,7 +9,6 @@
     class Meta:
         model = Role
         fields = ('id', 
-                  'role_id',
                   'role',
                   'role_type')
 
@@ -19,11 +18,10 @@
     id = serializers.Field()
     site = serializers.HyperlinkedRelatedField(view_name='site-detail')
     slice_memberships = serializers.HyperlinkedRelatedField(view_name='slice-membership-detail')
-    site_privileges = serializers.HyperlinkedRelatedField(view_name='site-privilege-detail')
+    site_privileges = serializers.HyperlinkedRelatedField(view_name='siteprivilege-detail')
     class Meta:
         model = User
         fields = ('id',
-                  'user_id', 
                   'kuser_id', 
                   'firstname', 
                   'lastname',
@@ -66,7 +64,7 @@
     user = serializers.HyperlinkedRelatedField(view_name='user-detail')
     role = serializers.HyperlinkedRelatedField(view_name='role-detail')
     class Meta:
-        model = SitePrivilege
+        model = SliceMembership
         fields = ('id',
                   'user',
                   'slice',
@@ -115,7 +113,7 @@
 
     # HyperlinkedModelSerializer doesn't include the id by default
     id = serializers.Field()
-    sites = serializers.HyperlinkedRelatedField(view_name='deploymentnetwork-detail')
+    sites = serializers.HyperlinkedRelatedField(view_name='site-detail')
     class Meta:
         model = Deployment
         fields = ('id',
@@ -127,9 +125,8 @@
     # HyperlinkedModelSerializer doesn't include the id by default
     id = serializers.Field()
     image = serializers.HyperlinkedRelatedField(view_name='image-detail')
-    key = serializers.HyperlinkedRelatedField(view_name='key-detail')
     slice = serializers.HyperlinkedRelatedField(view_name='slice-detail')
-    deployment_network = serializers.HyperlinkedRelatedField(view_name='deployment_network-detail')
+    deployment = serializers.HyperlinkedRelatedField(view_name='deployment-detail')
     node = serializers.HyperlinkedRelatedField(view_name='node-detail')
     
     
@@ -143,9 +140,8 @@
                   'instance_name',
                   'ip',
                   'image',
-                  'key',
                   'slice',
-                  'deploymentNetwork',
+                  'deployment',
                   'node')
 
 class NodeSerializer(serializers.HyperlinkedModelSerializer):
diff --git a/planetstack/core/views/deployment_networks.py b/planetstack/core/views/deployment_networks.py
deleted file mode 100644
index ef569ac..0000000
--- a/planetstack/core/views/deployment_networks.py
+++ /dev/null
@@ -1,59 +0,0 @@
-from django.http import Http404
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework import status
-
-from core.api.deployment_networks import add_deployment_network, delete_deployment_network, get_deployment_networks
-from core.serializers import DeploymentSerializer
-from util.request import parse_request
-
-
-class DeploymentListCreate(APIView):
-    """ 
-    List all deployment networks or create a new role.
-    """
-
-    def post(self, request, format = None):
-        data = parse_request(request.DATA)  
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)        
-        elif 'deploymentNetwork' in data:
-        
-            deployment = add_deployment_network(data['auth'], data['deploymentNetwork'].get('name'))
-            serializer = DeploymentSerializer(deployment)
-            return Response(serializer.data, status=status.HTTP_201_CREATED)
-        else:
-            deployment_networks = get_deployment_networks(data['auth'])
-            serializer = DeploymentSerializer(deployment_networks, many=True)
-            return Response(serializer.data)
-        
-            
-class DeploymentRetrieveUpdateDestroy(APIView):
-    """
-    Retrieve, update or delete a deployment network 
-    """
-
-    def post(self, request, pk, format=None):
-        """Retrieve a deployment network"""
-        data = parse_request(request.DATA)
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-        deployment_networks = get_deployment_networks(data['auth'], pk)
-        if not deployment_networks:
-            return Response(status=status.HTTP_404_NOT_FOUND)
-        serializer = DeploymentSerializer(deployment_networks[0])
-        return Response(serializer.data)                  
-
-    def put(self, request, pk, format=None):
-        """deployment network update not implemnted""" 
-        return Response(status=status.HTTP_404_NOT_FOUND) 
-
-    def delete(self, request, pk, format=None):
-        data = parse_request(request.DATA) 
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-        delete_deployment_network(data['auth'], pk)
-        return Response(status=status.HTTP_204_NO_CONTENT) 
-            
-            
-        
diff --git a/planetstack/core/views/deployments.py b/planetstack/core/views/deployments.py
new file mode 100644
index 0000000..285a53a
--- /dev/null
+++ b/planetstack/core/views/deployments.py
@@ -0,0 +1,12 @@
+from core.serializers import DeploymentSerializer
+from rest_framework import generics
+from core.models import Deployment
+
+class DeploymentList(generics.ListCreateAPIView):
+    queryset = Deployment.objects.all()
+    serializer_class = DeploymentSerializer
+
+class DeploymentDetail(generics.RetrieveUpdateDestroyAPIView):
+    queryset = Deployment.objects.all()
+    serializer_class = DeploymentSerializer
+
diff --git a/planetstack/core/views/images.py b/planetstack/core/views/images.py
index 7e0ab59..5ea5d76 100644
--- a/planetstack/core/views/images.py
+++ b/planetstack/core/views/images.py
@@ -1,55 +1,12 @@
-from django.http import Http404
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework import status
-
-from core.api.images import add_image, delete_image, get_images
 from core.serializers import ImageSerializer
-from util.request import parse_request
+from rest_framework import generics
+from core.models import Image
 
+class ImageList(generics.ListCreateAPIView):
+    queryset = Image.objects.all()
+    serializer_class = ImageSerializer
 
-class ImageListCreate(APIView):
-    """ 
-    List all images or create a new image.
-    """
+class ImageDetail(generics.RetrieveUpdateDestroyAPIView):
+    queryset = Image.objects.all()
+    serializer_class = ImageSerializer
 
-    def post(self, request, format = None):
-        data = parse_request(request.DATA)  
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)        
-        elif 'image' in data:
-            """Not Implemented"""
-            return Response(status=status.HTTP_404_NOT_FOUND)
-        else:
-            images = get_images(data['auth'])
-            serializer = ImageSerializer(images, many=True)
-            return Response(serializer.data)
-        
-            
-class ImageRetrieveUpdateDestroy(APIView):
-    """
-    Retrieve, update or delete an image  
-    """
-
-    def post(self, request, pk, format=None):
-        """Retrieve an image """
-        data = parse_request(request.DATA)
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-        images = get_images(data['auth'], pk)
-        if not images:
-            return Response(status=status.HTTP_404_NOT_FOUND)
-        serializer = ImageSerializer(images[0])
-        return Response(serializer.data)                  
-
-    def put(self, request, pk, format=None):
-        """update image not implemnted""" 
-        return Response(status=status.HTTP_404_NOT_FOUND) 
-
-    def delete(self, request, pk, format=None):
-        """delete image not implemnted""" 
-        return Response(status=status.HTTP_404_NOT_FOUND) 
-
-            
-            
-        
diff --git a/planetstack/core/views/nodes.py b/planetstack/core/views/nodes.py
index 0f1977e..8706114 100644
--- a/planetstack/core/views/nodes.py
+++ b/planetstack/core/views/nodes.py
@@ -1,55 +1,13 @@
-from django.http import Http404
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework import status
-
-from core.api.nodes import add_node, delete_node, get_nodes, update_node
 from core.serializers import NodeSerializer
-from util.request import parse_request
+from rest_framework import generics
+from core.models import Node
+
+class NodeList(generics.ListCreateAPIView):
+    queryset = Node.objects.all()
+    serializer_class = NodeSerializer
+
+class NodeDetail(generics.RetrieveUpdateDestroyAPIView):
+    queryset = Node.objects.all()
+    serializer_class = NodeSerializer
 
 
-class NodeListCreate(APIView):
-    """ 
-    List all nodes or create a new node.
-    """
-
-    def post(self, request, format = None):
-        data = parse_request(request.DATA)  
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)        
-        elif 'node' in data:
-            """Not Implemented"""
-            return Response(status=status.HTTP_404_NOT_FOUND)
-        else:
-            nodes = get_nodes(data['auth'])
-            serializer = NodeSerializer(nodes, many=True)
-            return Response(serializer.data)
-        
-            
-class NodeRetrieveUpdateDestroy(APIView):
-    """
-    Retrieve, update or delete an node  
-    """
-
-    def post(self, request, pk, format=None):
-        """Retrieve an node """
-        data = parse_request(request.DATA)
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-        nodes = get_nodes(data['auth'], pk)
-        if not nodes:
-            return Response(status=status.HTTP_404_NOT_FOUND)
-        serializer = NodeSerializer(nodes[0])
-        return Response(serializer.data)                  
-
-    def put(self, request, pk, format=None):
-        """update node not implemnted""" 
-        return Response(status=status.HTTP_404_NOT_FOUND) 
-
-    def delete(self, request, pk, format=None):
-        """delete node not implemnted""" 
-        return Response(status=status.HTTP_404_NOT_FOUND) 
-
-            
-            
-        
diff --git a/planetstack/core/views/projects.py b/planetstack/core/views/projects.py
new file mode 100644
index 0000000..c5311d5
--- /dev/null
+++ b/planetstack/core/views/projects.py
@@ -0,0 +1,13 @@
+from core.serializers import ProjectSerializer
+from rest_framework import generics
+from core.models import Project
+
+class ProjectList(generics.ListCreateAPIView):
+    queryset = Project.objects.all()
+    serializer_class = ProjectSerializer
+
+class ProjectDetail(generics.RetrieveUpdateDestroyAPIView):
+    queryset = Project.objects.all()
+    serializer_class = ProjectSerializer
+
+
diff --git a/planetstack/core/views/reservations.py b/planetstack/core/views/reservations.py
new file mode 100644
index 0000000..03f79eb
--- /dev/null
+++ b/planetstack/core/views/reservations.py
@@ -0,0 +1,13 @@
+from core.serializers import ReservationSerializer
+from rest_framework import generics
+from core.models import Reservation
+
+class ReservationList(generics.ListCreateAPIView):
+    queryset = Reservation.objects.all()
+    serializer_class = ReservationSerializer
+
+class ReservationDetail(generics.RetrieveUpdateDestroyAPIView):
+    queryset = Reservation.objects.all()
+    serializer_class = ReservationSerializer
+
+
diff --git a/planetstack/core/views/roles.py b/planetstack/core/views/roles.py
index 37bb149..13c9917 100644
--- a/planetstack/core/views/roles.py
+++ b/planetstack/core/views/roles.py
@@ -1,58 +1,13 @@
-from django.http import Http404
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework import status
-
-from core.api.roles import add_role, delete_role, get_roles
 from core.serializers import RoleSerializer
-from util.request import parse_request
+from rest_framework import generics
+from core.models import Role
+
+class RoleList(generics.ListCreateAPIView):
+    queryset = Role.objects.all()
+    serializer_class = RoleSerializer
+
+class RoleDetail(generics.RetrieveUpdateDestroyAPIView):
+    queryset = Role.objects.all()
+    serializer_class = RoleSerializer
 
 
-class RoleListCreate(APIView):
-    """ 
-    List all roles or create a new role.
-    """
-
-    def post(self, request, format = None):
-        data = parse_request(request.DATA)  
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)        
-        elif 'role' in data:
-            role = add_role(data['auth'], data['role']['role_type'])
-            serializer = RoleSerializer(data=role)
-            return Response(serializer.data, status=status.HTTP_201_CREATED)
-        else:
-            roles = get_roles(data['auth'])
-            serializer = RoleSerializer(roles, many=True)
-            return Response(serializer.data)
-        
-            
-class RoleRetrieveUpdateDestroy(APIView):
-    """
-    Retrieve, update or delete a role 
-    """
-
-    def post(self, request, pk, format=None):
-        """Retrieve a role"""
-        data = parse_request(request.DATA)
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-        roles = get_roles(data['auth'], pk)
-        if not roles:
-            return Response(status=status.HTTP_404_NOT_FOUND)
-        serializer = RoleSerializer(roles[0])
-        return Response(serializer.data)                  
-
-    def put(self, request, pk, format=None):
-        """role update not implemnted""" 
-        return Response(status=status.HTTP_404_NOT_FOUND) 
-
-    def delete(self, request, pk, format=None):
-        data = parse_request(request.DATA) 
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-        delete_role(data['auth'], pk)
-        return Response(status=status.HTTP_204_NO_CONTENT) 
-            
-            
-        
diff --git a/planetstack/core/views/serviceclasses.py b/planetstack/core/views/serviceclasses.py
new file mode 100644
index 0000000..b8b1b70
--- /dev/null
+++ b/planetstack/core/views/serviceclasses.py
@@ -0,0 +1,13 @@
+from core.serializers import ServiceClassSerializer
+from rest_framework import generics
+from core.models import ServiceClass
+
+class ServiceClassList(generics.ListCreateAPIView):
+    queryset = ServiceClass.objects.all()
+    serializer_class = ServiceClassSerializer
+
+class ServiceClassDetail(generics.RetrieveUpdateDestroyAPIView):
+    queryset = ServiceClass.objects.all()
+    serializer_class = ServiceClassSerializer
+
+
diff --git a/planetstack/core/views/serviceresources.py b/planetstack/core/views/serviceresources.py
new file mode 100644
index 0000000..e394c18
--- /dev/null
+++ b/planetstack/core/views/serviceresources.py
@@ -0,0 +1,13 @@
+from core.serializers import ServiceResourceSerializer
+from rest_framework import generics
+from core.models import ServiceResource
+
+class ServiceResourceList(generics.ListCreateAPIView):
+    queryset = ServiceResource.objects.all()
+    serializer_class = ServiceResourceSerializer
+
+class ServiceResourceDetail(generics.RetrieveUpdateDestroyAPIView):
+    queryset = ServiceResource.objects.all()
+    serializer_class = ServiceResourceSerializer
+
+
diff --git a/planetstack/core/views/site_privileges.py b/planetstack/core/views/site_privileges.py
index 37fc371..90053e5 100644
--- a/planetstack/core/views/site_privileges.py
+++ b/planetstack/core/views/site_privileges.py
@@ -1,66 +1,13 @@
-from django.http import Http404
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework import status
-
-from core.api.site_privileges import add_site_privilege, delete_site_privilege, get_site_privileges, update_site_privilege
 from core.serializers import SitePrivilegeSerializer
-from util.request import parse_request
+from rest_framework import generics
+from core.models import SitePrivilege
+
+class SitePrivilegeList(generics.ListCreateAPIView):
+    queryset = SitePrivilege.objects.all()
+    serializer_class = SitePrivilegeSerializer
+
+class SitePrivilegeDetail(generics.RetrieveUpdateDestroyAPIView):
+    queryset = SitePrivilege.objects.all()
+    serializer_class = SitePrivilegeSerializer
 
 
-class SitePrivilegeListCreate(APIView):
-    """ 
-    List all site_privileges or create a new site_privilege.
-    """
-
-    def post(self, request, format = None):
-        data = parse_request(request.DATA)  
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)        
-        elif 'site_privilege' in data:
-            site_privilege = add_site_privilege(data['auth'], data['site_privilege'])
-            serializer = SitePrivilegeSerializer(site_privilege)
-            return Response(serializer.data, status=status.HTTP_201_CREATED)
-        else:
-            site_privileges = get_site_privileges(data['auth'])
-            serializer = SitePrivilegeSerializer(site_privileges, many=True)
-            return Response(serializer.data)
-        
-            
-class SitePrivilegeRetrieveUpdateDestroy(APIView):
-    """
-    Retrieve, update or delete a site_privilege 
-    """
-
-    def post(self, request, pk, format=None):
-        """Retrieve a site_privilege"""
-        data = parse_request(request.DATA)
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-        site_privileges = get_site_privileges(data['auth'], pk)
-        if not site_privileges:
-            return Response(status=status.HTTP_404_NOT_FOUND)
-        serializer = SitePrivilegeSerializer(site_privileges[0])
-        return Response(serializer.data)                  
-
-    def put(self, request, pk, format=None):
-        """update a site_privilege""" 
-        data = parse_request(request.DATA)
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-        elif 'site_privilege' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-
-        site_privilege = update_site_privilege(pk, data['site_privilege'])
-        serializer = SitePrivilegeSerializer(site_privilege)
-        return Response(serializer.data) 
-
-    def delete(self, request, pk, format=None):
-        data = parse_request(request.DATA) 
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-        delete_site_privilege(data['auth'], pk)
-        return Response(status=status.HTTP_204_NO_CONTENT) 
-            
-            
-        
diff --git a/planetstack/core/views/sites.py b/planetstack/core/views/sites.py
index 6449b67..4ec9cb2 100644
--- a/planetstack/core/views/sites.py
+++ b/planetstack/core/views/sites.py
@@ -1,66 +1,11 @@
-from django.http import Http404
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework import status
-
-from core.api.sites import add_site, delete_site, get_sites
 from core.serializers import SiteSerializer
-from util.request import parse_request
+from rest_framework import generics
+from core.models import Site
 
+class SiteList(generics.ListCreateAPIView):
+    queryset = Site.objects.all()
+    serializer_class = SiteSerializer
 
-class SiteListCreate(APIView):
-    """ 
-    List all sites or create a new site.
-    """
-
-    def post(self, request, format = None):
-        data = parse_request(request.DATA)  
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)        
-        elif 'site' in data:
-            site = add_site(data['auth'], data['site'])
-            serializer = SiteSerializer(site)
-            return Response(serializer.data, status=status.HTTP_201_CREATED)
-        else:
-            sites = get_sites(data['auth'])
-            serializer = SiteSerializer(sites, many=True)
-            return Response(serializer.data)
-        
-            
-class SiteRetrieveUpdateDestroy(APIView):
-    """
-    Retrieve, update or delete a site 
-    """
-
-    def post(self, request, pk, format=None):
-        """Retrieve a site"""
-        data = parse_request(request.DATA)
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-        sites = get_sites(data['auth'], {'id': pk})
-        if not sites:
-            return Response(status=status.HTTP_404_NOT_FOUND)
-        serializer = SiteSerializer(sites[0])
-        return Response(serializer.data)                  
-
-    def put(self, request, pk, format=None):
-        """update a site""" 
-        data = parse_request(request.DATA)
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-        elif 'site' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-
-        site = update_site(pk, data['site'])
-        serializer = SiteSerializer(site)
-        return Response(serializer.data) 
-
-    def delete(self, request, pk, format=None):
-        data = parse_request(request.DATA) 
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-        delete_site(data['auth'], {'id': pk})
-        return Response(status=status.HTTP_204_NO_CONTENT) 
-            
-            
-        
+class SiteDetail(generics.RetrieveUpdateDestroyAPIView):
+    queryset = Site.objects.all()
+    serializer_class = SiteSerializer
diff --git a/planetstack/core/views/slice_memberships.py b/planetstack/core/views/slice_memberships.py
index 4bb581c..13f0707 100644
--- a/planetstack/core/views/slice_memberships.py
+++ b/planetstack/core/views/slice_memberships.py
@@ -1,66 +1,13 @@
-from django.http import Http404
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework import status
-
-from core.api.slice_memberships import add_slice_membership, delete_slice_membership, get_slice_memberships, update_slice_membership
 from core.serializers import SliceMembershipSerializer
-from util.request import parse_request
+from rest_framework import generics
+from core.models import SliceMembership
+
+class SliceMembershipList(generics.ListCreateAPIView):
+    queryset = SliceMembership.objects.all()
+    serializer_class = SliceMembershipSerializer
+
+class SliceMembershipDetail(generics.RetrieveUpdateDestroyAPIView):
+    queryset = SliceMembership.objects.all()
+    serializer_class = SliceMembershipSerializer
 
 
-class SliceMembershipListCreate(APIView):
-    """ 
-    List all slice_memberships or create a new slice_membership.
-    """
-
-    def post(self, request, format = None):
-        data = parse_request(request.DATA)  
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)        
-        elif 'slice_membership' in data:
-            slice_membership = add_slice_membership(data['auth'], data['slice_membership'])
-            serializer = SliceMembershipSerializer(slice_membership)
-            return Response(serializer.data, status=status.HTTP_201_CREATED)
-        else:
-            slice_memberships = get_slice_memberships(data['auth'])
-            serializer = SliceMembershipSerializer(slice_memberships, many=True)
-            return Response(serializer.data)
-        
-            
-class SliceMembershipRetrieveUpdateDestroy(APIView):
-    """
-    Retrieve, update or delete a slice_membership 
-    """
-
-    def post(self, request, pk, format=None):
-        """Retrieve a slice_membership"""
-        data = parse_request(request.DATA)
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-        slice_memberships = get_slice_memberships(data['auth'], pk)
-        if not slice_memberships:
-            return Response(status=status.HTTP_404_NOT_FOUND)
-        serializer = SliceMembershipSerializer(slice_memberships[0])
-        return Response(serializer.data)                  
-
-    def put(self, request, pk, format=None):
-        """update a slice_membership""" 
-        data = parse_request(request.DATA)
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-        elif 'slice_membership' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-
-        slice_membership = update_slice_membership(pk, data['slice_membership'])
-        serializer = SliceMembershipSerializer(slice_membership)
-        return Response(serializer.data) 
-
-    def delete(self, request, pk, format=None):
-        data = parse_request(request.DATA) 
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-        delete_slice_membership(data['auth'], pk)
-        return Response(status=status.HTTP_204_NO_CONTENT) 
-            
-            
-        
diff --git a/planetstack/core/views/slices.py b/planetstack/core/views/slices.py
index 5954d0c..e3ab139 100644
--- a/planetstack/core/views/slices.py
+++ b/planetstack/core/views/slices.py
@@ -1,66 +1,13 @@
-from django.http import Http404
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework import status
-
-from core.api.slices import add_slice, delete_slice, get_slices, update_slice
 from core.serializers import SliceSerializer
-from util.request import parse_request
+from rest_framework import generics
+from core.models import Slice
+
+class SliceList(generics.ListCreateAPIView):
+    queryset = Slice.objects.all()
+    serializer_class = SliceSerializer
+
+class SliceDetail(generics.RetrieveUpdateDestroyAPIView):
+    queryset = Slice.objects.all()
+    serializer_class = SliceSerializer
 
 
-class SliceListCreate(APIView):
-    """ 
-    List all slices or create a new slice.
-    """
-
-    def post(self, request, format = None):
-        data = parse_request(request.DATA)  
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)        
-        elif 'slice' in data:
-            slice = add_slice(data['auth'], data['slice'])
-            serializer = SliceSerializer(slice)
-            return Response(serializer.data, status=status.HTTP_201_CREATED)
-        else:
-            slices = get_slices(data['auth'])
-            serializer = SliceSerializer(slices, many=True)
-            return Response(serializer.data)
-        
-            
-class SliceRetrieveUpdateDestroy(APIView):
-    """
-    Retrieve, update or delete a slice 
-    """
-
-    def post(self, request, pk, format=None):
-        """Retrieve a slice"""
-        data = parse_request(request.DATA)
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-        slices = get_slices(data['auth'],  pk)
-        if not slices:
-            return Response(status=status.HTTP_404_NOT_FOUND)
-        serializer = SliceSerializer(slices[0])
-        return Response(serializer.data)                  
-
-    def put(self, request, pk, format=None):
-        """update a slice""" 
-        data = parse_request(request.DATA)
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-        elif 'slice' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-
-        slice = update_slice(pk, data['slice'])
-        serializer = SliceSerializer(slice)
-        return Response(serializer.data) 
-
-    def delete(self, request, pk, format=None):
-        data = parse_request(request.DATA) 
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-        delete_slice(data['auth'],  pk)
-        return Response(status=status.HTTP_204_NO_CONTENT) 
-            
-            
-        
diff --git a/planetstack/core/views/slivers.py b/planetstack/core/views/slivers.py
index 3741cce..bb310da 100644
--- a/planetstack/core/views/slivers.py
+++ b/planetstack/core/views/slivers.py
@@ -1,66 +1,13 @@
-from django.http import Http404
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework import status
-
-from core.api.slivers import add_sliver, delete_sliver, get_slivers, update_sliver
 from core.serializers import SliverSerializer
-from util.request import parse_request
+from rest_framework import generics
+from core.models import Sliver
+
+class SliverList(generics.ListCreateAPIView):
+    queryset = Sliver.objects.all()
+    serializer_class = SliverSerializer
+
+class SliverDetail(generics.RetrieveUpdateDestroyAPIView):
+    queryset = Sliver.objects.all()
+    serializer_class = SliverSerializer
 
 
-class SliverListCreate(APIView):
-    """ 
-    List all slivers or create a new sliver.
-    """
-
-    def post(self, request, format = None):
-        data = parse_request(request.DATA)  
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)        
-        elif 'sliver' in data:
-            sliver = add_sliver(data['auth'], data['sliver'])
-            serializer = SliverSerializer(sliver)
-            return Response(serializer.data, status=status.HTTP_201_CREATED)
-        else:
-            slivers = get_slivers(data['auth'])
-            serializer = SliverSerializer(slivers, many=True)
-            return Response(serializer.data)
-        
-            
-class SliverRetrieveUpdateDestroy(APIView):
-    """
-    Retrieve, update or delete a sliver 
-    """
-
-    def post(self, request, pk, format=None):
-        """Retrieve a sliver"""
-        data = parse_request(request.DATA)
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-        slivers = get_slivers(data['auth'], pk)
-        if not slivers:
-            return Response(status=status.HTTP_404_NOT_FOUND)
-        serializer = SliverSerializer(slivers[0])
-        return Response(serializer.data)                  
-
-    def put(self, request, pk, format=None):
-        """update a sliver""" 
-        data = parse_request(request.DATA)
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-        elif 'sliver' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-
-        sliver = update_sliver(pk, data['sliver'])
-        serializer = SliverSerializer(sliver)
-        return Response(serializer.data) 
-
-    def delete(self, request, pk, format=None):
-        data = parse_request(request.DATA) 
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-        delete_sliver(data['auth'], pk)
-        return Response(status=status.HTTP_204_NO_CONTENT) 
-            
-            
-        
diff --git a/planetstack/core/views/tags.py b/planetstack/core/views/tags.py
new file mode 100644
index 0000000..ba8a035
--- /dev/null
+++ b/planetstack/core/views/tags.py
@@ -0,0 +1,13 @@
+from core.serializers import TagSerializer
+from rest_framework import generics
+from core.models import Tag
+
+class TagList(generics.ListCreateAPIView):
+    queryset = Tag.objects.all()
+    serializer_class = TagSerializer
+
+class TagDetail(generics.RetrieveUpdateDestroyAPIView):
+    queryset = Tag.objects.all()
+    serializer_class = TagSerializer
+
+
diff --git a/planetstack/core/views/users.py b/planetstack/core/views/users.py
index 8b27928..06ac0f3 100644
--- a/planetstack/core/views/users.py
+++ b/planetstack/core/views/users.py
@@ -1,66 +1,11 @@
-from django.http import Http404
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework import status
-
-from core.api.users import add_user, delete_user, get_users, update_user
 from core.serializers import UserSerializer
-from util.request import parse_request
+from rest_framework import generics
+from core.models import User
 
+class UserList(generics.ListCreateAPIView):
+    queryset = User.objects.all()
+    serializer_class = UserSerializer
 
-class UserListCreate(APIView):
-    """ 
-    List all users or create a new user.
-    """
-
-    def post(self, request, format = None):
-        data = parse_request(request.DATA)  
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)        
-        elif 'user' in data:
-            user = add_user(data['auth'], data['user'])
-            serializer = UserSerializer(user)
-            return Response(serializer.data, status=status.HTTP_201_CREATED)
-        else:
-            users = get_users(data['auth'])
-            serializer = UserSerializer(users, many=True)
-            return Response(serializer.data)
-        
-            
-class UserRetrieveUpdateDestroy(APIView):
-    """
-    Retrieve, update or delete a user 
-    """
-
-    def post(self, request, pk, format=None):
-        """Retrieve a user"""
-        data = parse_request(request.DATA)
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-        users = get_users(data['auth'], {'id': pk})
-        if not users:
-            return Response(status=status.HTTP_404_NOT_FOUND)
-        serializer = UserSerializer(users[0])
-        return Response(serializer.data)                  
-
-    def put(self, request, pk, format=None):
-        """update a user""" 
-        data = parse_request(request.DATA)
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-        elif 'user' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-
-        user = update_user(pk, data['user'])
-        serializer = UserSerializer(user)
-        return Response(serializer.data) 
-
-    def delete(self, request, pk, format=None):
-        data = parse_request(request.DATA) 
-        if 'auth' not in data:
-            return Response(status=status.HTTP_400_BAD_REQUEST)
-        delete_user(data['auth'], {'id': pk})
-        return Response(status=status.HTTP_204_NO_CONTENT) 
-            
-            
-        
+class UserDetail(generics.RetrieveUpdateDestroyAPIView):
+    queryset = User.objects.all()
+    serializer_class = UserSerializer
diff --git a/planetstack/openstack/backend.py b/planetstack/openstack/backend.py
index 43afba7..2f4aa71 100644
--- a/planetstack/openstack/backend.py
+++ b/planetstack/openstack/backend.py
@@ -1,6 +1,6 @@
 import threading
 from openstack.observer import OpenStackObserver
-from openstack.event_listener import EventListener
+from openstack.event_manager import EventListener
 
 class Backend:
     
@@ -11,7 +11,7 @@
         observer_thread.start()
 
         # start event listene
-        event_listener = EventListener()
-        event_listener_thread = threading.Thread(target=event_listener.run)
-        event_listener_thread.start()
+        event_manager = EventListener(wake_up=observer.wake_up)
+        event_manager_thread = threading.Thread(target=event_manager.run)
+        event_manager_thread.start()
                 
diff --git a/planetstack/openstack/driver.py b/planetstack/openstack/driver.py
index b9faa97..5bb1eb2 100644
--- a/planetstack/openstack/driver.py
+++ b/planetstack/openstack/driver.py
@@ -18,12 +18,12 @@
         else:
             self.shell = OpenStackClient()
 
-    def create_role(self, name): 
+    def create_role(self, name):
         roles = self.shell.keystone.roles.findall(name=name)
         if not roles:
             role = self.shell.keystone.roles.create(name)
         else:
-            role = roles[0] 
+            role = roles[0]
         return role
 
     def delete_role(self, filter):
@@ -56,9 +56,9 @@
         for tenant in tenants:
             # nova does not automatically delete the tenant's instances
             # so we manually delete instances before deleteing the tenant   
-            instances = self.shell.nova_db.instance_get_all_by_filters(ctx, 
+            instances = self.shell.nova_db.instance_get_all_by_filters(ctx,
                        {'project_id': tenant.id}, 'id', 'asc')
-            client = OpenStackClient(tenant=tenant)
+            client = OpenStackClient(tenant=tenant.name)
             driver = OpenStackDriver(client=client)
             for instance in instances:
                 driver.destroy_instance(instance.id)
@@ -160,12 +160,12 @@
         if router and subnet:
             self.shell.quantum.remove_interface_router(router_id, {'subnet_id': subnet_id})
  
-    def create_network(self, name):
+    def create_network(self, name, shared=False):
         nets = self.shell.quantum.list_networks(name=name)['networks']
         if nets: 
             net = nets[0]
         else:
-            net = self.shell.quantum.create_network({'network': {'name': name}})['network']
+            net = self.shell.quantum.create_network({'network': {'name': name, 'shared': shared}})['network']
         return net
  
     def delete_network(self, id):
@@ -210,7 +210,7 @@
         for snet in subnets:
             if snet['cidr'] == cidr_ip and snet['network_id'] == network_id:
                 subnet = snet
- 
+
         if not subnet:
             allocation_pools = [{'start': start, 'end': end}]
             subnet = {'subnet': {'name': name,
@@ -218,7 +218,7 @@
                                  'ip_version': ip_version,
                                  'cidr': cidr_ip,
                                  'dns_nameservers': ['8.8.8.8', '8.8.4.4'],
-                                 'allocation_pools': allocation_pools}}          
+                                 'allocation_pools': allocation_pools}}
             subnet = self.shell.quantum.create_subnet(subnet)['subnet']
             self.add_external_route(subnet)
         # TODO: Add route to external network
@@ -239,7 +239,15 @@
                 self.delete_external_route(subnet)
         return 1
 
-    def add_external_route(self, subnet):
+    def get_external_routes(self):
+        status, output = commands.getstatusoutput('route')
+        routes = output.split('\n')[3:]
+        return routes
+
+    def add_external_route(self, subnet, routes=[]):
+        if not routes:
+            routes = self.get_external_routes()
+ 
         ports = self.shell.quantum.list_ports()['ports']
 
         gw_ip = subnet['gateway_ip']
@@ -256,14 +264,23 @@
                     gw_port = port
                     router_id = gw_port['device_id']
                     router = self.shell.quantum.show_router(router_id)['router']
-                    ext_net = router['external_gateway_info']['network_id']
-                    for port in ports:
-                        if port['device_id'] == router_id and port['network_id'] == ext_net:
-                            ip_address = port['fixed_ips'][0]['ip_address']
+                    if router and router.get('external_gateway_info'):
+                        ext_net = router['external_gateway_info']['network_id']
+                        for port in ports:
+                            if port['device_id'] == router_id and port['network_id'] == ext_net:
+                                ip_address = port['fixed_ips'][0]['ip_address']
 
         if ip_address:
-            cmd = "route add -net %s dev br-ex gw %s" % (subnet['cidr'], ip_address)
-            commands.getstatusoutput(cmd)
+            # check if external route already exists
+            route_exists = False
+            if routes:
+                for route in routes:
+                    if subnet['cidr'] in route and ip_address in route:
+                        route_exists = True
+            if not route_exists:
+                cmd = "route add -net %s dev br-ex gw %s" % (subnet['cidr'], ip_address)
+                s, o = commands.getstatusoutput(cmd)
+                #print cmd, "\n", s, o
 
         return 1
 
@@ -311,9 +328,37 @@
         keys = self.shell.nova.keypairs.findall(id=id)
         for key in keys:
             self.shell.nova.keypairs.delete(key) 
-        return 1 
+        return 1
 
-    def spawn_instance(self, name, key_name=None, hostname=None, image_id=None, security_group=None, pubkeys=[]):
+    def get_private_networks(self, tenant=None):
+        if not tenant:
+            tenant = self.shell.nova.tenant
+        tenant = self.shell.keystone.tenants.find(name=tenant)
+        search_opts = {"tenant_id": tenant.id, "shared": False}
+        private_networks = self.shell.quantum.list_networks(**search_opts)
+        return private_networks
+
+    def get_shared_networks(self):
+        search_opts = {"shared": True}
+        shared_networks = self.shell.quantum.list_networks(**search_opts)
+        return shared_networks
+
+    def get_network_subnet(self, network_id):
+        subnet_id = None
+        subnet = None
+        if network_id:
+            os_networks = self.shell.quantum.list_networks(id=network_id)["networks"]
+            if os_networks:
+                os_network = os_networks[0]
+                if os_network['subnets']:
+                    subnet_id = os_network['subnets'][0]
+                    os_subnets = self.shell.quantum.list_subnets(id=subnet_id)['subnets']
+                    if os_subnets:
+                        subnet = os_subnets[0]['cidr']
+
+        return (subnet_id, subnet)
+
+    def spawn_instance(self, name, key_name=None, hostname=None, image_id=None, security_group=None, pubkeys=[], nics=None):
         flavor_name = self.config.nova_default_flavor
         flavor = self.shell.nova.flavors.find(name=flavor_name)
         #if not image:
@@ -337,7 +382,8 @@
                                             security_group = security_group,
                                             files=files,
                                             scheduler_hints=hints,
-                                            availability_zone=availability_zone)
+                                            availability_zone=availability_zone,
+                                            nics=nics)
         return server
           
     def destroy_instance(self, id):
diff --git a/planetstack/openstack/event_listener.py b/planetstack/openstack/event_manager.py
similarity index 68%
rename from planetstack/openstack/event_listener.py
rename to planetstack/openstack/event_manager.py
index 5e0afff..ab50f66 100644
--- a/planetstack/openstack/event_listener.py
+++ b/planetstack/openstack/event_manager.py
@@ -1,11 +1,13 @@
 import threading
 import requests, json
+
 from core.models import *
-from openstack.manager import OpenStackManager
+#from openstack.manager import OpenStackManager
+from planetstack.config import Config
 
 import os
 import base64
-import fofum
+from fofum import Fofum
 
 # decorator that marks dispatachable event methods  
 def event(func):
@@ -13,9 +15,9 @@
     return func      
 
 class EventHandler:
-
+    # This code is currently not in use.
     def __init__(self):
-        self.manager = OpenStackManager()
+        pass #self.manager = OpenStackManager()
 
     @staticmethod
     def get_events():
@@ -82,22 +84,52 @@
         self.manager.destroy_instance(instance_id)                            
 
     
+class EventSender:
+    def __init__(self,user=None,clientid=None):
+        try:
+            clid = Config().feefie_client_id
+            user = Config().feefie_client_user
+        except:
+            clid = 'planetstack_core_team'
+            user = 'pl'
+
+        self.fofum = Fofum(user=user)
+        self.fofum.make(clid)
+
+    def fire(self):
+        self.fofum.fire()
 
 class EventListener:
-    def __init__(self):
+    def __init__(self,wake_up=None):
         self.handler = EventHandler()
+        self.wake_up = wake_up
 
     def handle_event(self, payload):
         payload_dict = json.loads(payload)
-        event = payload_dict['event']
-        ctx = payload_dict['ctx']
-        self.handler.dispatch(event,**ctx)   
+
+	# The code below will come back when we optimize the observer syncs
+	# into 'small' and 'big' syncs.
+
+        #event = payload_dict['event']
+        #ctx = payload_dict['ctx']
+        #self.handler.dispatch(event,**ctx)   
+
+        if (self.wake_up):
+            self.wake_up()
+        
 
     def run(self):
         # This is our unique client id, to be used when firing and receiving events
-        clid = base64.urlsafe_b64encode(os.urandom(12))
+        # It needs to be generated once and placed in the config file
 
-        f = Fofum()
+        try:
+            clid = Config().feefie_client_id
+            user = Config().feefie_client_user
+        except:
+            clid = 'planetstack_core_team'
+            user = 'pl'
+
+        f = Fofum(user=user)
         
         listener_thread = threading.Thread(target=f.listen_for_event,args=(clid,self.handle_event))
         listener_thread.start()
diff --git a/planetstack/openstack/manager.py b/planetstack/openstack/manager.py
index 3ae7dea..2fb4ff8 100644
--- a/planetstack/openstack/manager.py
+++ b/planetstack/openstack/manager.py
@@ -301,17 +301,32 @@
             #del_route = 'route del -net %s' % self.cidr
             #commands.getstatusoutput(del_route)
 
+    def get_requested_networks(self, slice):
+        network_ids = [x.network_id for x in slice.networks.all()]
+
+        if slice.network_id is not None:
+            network_ids.append(slice.network_id)
+
+        networks = []
+        for network_id in network_ids:
+            networks.append({"net-id": network_id})
+
+        return networks
+
     @require_enabled
     def save_sliver(self, sliver):
         if not sliver.instance_id:
+            nics = self.get_requested_networks(sliver.slice)
+            file("/tmp/scott-manager","a").write("slice: %s\nreq: %s\n" % (str(sliver.slice.name), str(nics)))
             slice_memberships = SliceMembership.objects.filter(slice=sliver.slice)
             pubkeys = [sm.user.public_key for sm in slice_memberships if sm.user.public_key]
-            pubkeys.append(sliver.creator.public_key) 
+            pubkeys.append(sliver.creator.public_key)
             instance = self.driver.spawn_instance(name=sliver.name,
                                    key_name = sliver.creator.keyname,
                                    image_id = sliver.image.image_id,
                                    hostname = sliver.node.name,
-                                   pubkeys = pubkeys )
+                                   pubkeys = pubkeys,
+                                   nics = nics )
             sliver.instance_id = instance.id
             sliver.instance_name = getattr(instance, 'OS-EXT-SRV-ATTR:instance_name')
 
@@ -368,7 +383,7 @@
         from core.models.image import Image
         # collect local images
         images = Image.objects.all()
-        images_dict = {}    
+        images_dict = {}
         for image in images:
             images_dict[image.name] = image
 
@@ -391,4 +406,149 @@
         old_image_names = set(images_dict.keys()).difference(glance_images_dict.keys())
         Image.objects.filter(name__in=old_image_names).delete()
 
+    @require_enabled
+    def save_network(self, network):
+        if not network.network_id:
+            if network.template.sharedNetworkName:
+                network.network_id = network.template.sharedNetworkId
+                (network.subnet_id, network.subnet) = self.driver.get_network_subnet(network.network_id)
+            else:
+                network_name = network.name
+
+                # create network
+                os_network = self.driver.create_network(network_name, shared=True)
+                network.network_id = os_network['id']
+
+                # create router
+                router = self.driver.create_router(network_name)
+                network.router_id = router['id']
+
+                # create subnet
+                next_subnet = self.get_next_subnet()
+                cidr = str(next_subnet.cidr)
+                ip_version = next_subnet.version
+                start = str(next_subnet[2])
+                end = str(next_subnet[-2])
+                subnet = self.driver.create_subnet(name=network_name,
+                                                   network_id = network.network_id,
+                                                   cidr_ip = cidr,
+                                                   ip_version = ip_version,
+                                                   start = start,
+                                                   end = end)
+                network.subnet = cidr
+                network.subnet_id = subnet['id']
+                # add subnet as interface to slice's router
+                self.driver.add_router_interface(router['id'], subnet['id'])
+                # add external route
+                self.driver.add_external_route(subnet)
+
+        network.save()
+        network.enacted = datetime.now()
+        network.save(update_fields=['enacted'])
+
+    def delete_network(self, network):
+        if (network.router_id) and (network.subnet_id):
+            self.driver.delete_router_interface(network.router_id, network.subnet_id)
+        if network.subnet_id:
+            self.driver.delete_subnet(network.subnet_id)
+        if network.router_id:
+            self.driver.delete_router(network.router_id)
+        if network.network_id:
+            self.driver.delete_network(network.network_id)
+
+    def save_network_template(self, template):
+        if (template.sharedNetworkName) and (not template.sharedNetworkId):
+            os_networks = self.driver.shell.quantum.list_networks(name=template.sharedNetworkName)['networks']
+            if os_networks:
+                template.sharedNetworkId = os_networks[0]["id"]
+
+        template.save()
+        template.enacted = datetime.now()
+        template.save(update_fields=['enacted'])
+
+    def find_or_make_template_for_network(self, name):
+        """ Given a network name, try to guess the right template for it """
+
+        # templates for networks we may encounter
+        if name=='nat-net':
+            template_dict = None # {"name": "private-nat", "visibility": "private", "translation": "nat"}
+        elif name=='sharednet1':
+            template_dict = {"name": "dedicated-public", "visibility": "public", "translation": "none"}
+        else:
+            template_dict = {"name": "private", "visibility": "private", "translation": "none"}
+
+        # if we have an existing template return it
+        templates = NetworkTemplate.objects.filter(name=template_dict["name"])
+        if templates:
+            return templates[0]
+
+        if template_dict == None:
+            return None
+
+        template = NetworkTemplate(**template_dict)
+        template.save()
+        return template
+
+    def refresh_network_templates(self):
+        for template in NetworkTemplate.objects.all():
+            if (template.sharedNetworkName) and (not template.sharedNetworkId):
+                 # this will cause us to try to fill in the sharedNetworkId
+                 self.save_network_template(template)
+
+    def refresh_networks(self):
+        # get a list of all networks in the model
+
+        networks = Network.objects.all()
+        networks_by_name = {}
+        networks_by_id = {}
+        for network in networks:
+            networks_by_name[network.name] = network
+            networks_by_id[network.network_id] = network
+
+        # Get a list of all shared networks in OS
+
+        os_networks = self.driver.shell.quantum.list_networks()['networks']
+        os_networks_by_name = {}
+        os_networks_by_id = {}
+        for os_network in os_networks:
+            os_networks_by_name[os_network['name']] = os_network
+            os_networks_by_id[os_network['id']] = os_network
+
+        for (uuid, os_network) in os_networks_by_id.items():
+            #print "checking OS network", os_network['name']
+            if (os_network['shared']) and (uuid not in networks_by_id):
+                # Only automatically create shared networks. This is for Andy's
+                # nat-net and sharednet1.
+
+                owner_slice = Slice.objects.get(tenant_id = os_network['tenant_id'])
+                template = self.find_or_make_template_for_network(os_network['name'])
+
+                if (template is None):
+                    # This is our way of saying we don't want to auto-instantiate
+                    # this network type.
+                    continue
+
+                (subnet_id, subnet) = self.driver.get_network_subnet(os_network['id'])
+
+                if owner_slice:
+                    #print "creating model object for OS network", os_network['name']
+                    new_network = Network(name = os_network['name'],
+                                          template = template,
+                                          owner = owner_slice,
+                                          network_id = uuid,
+                                          subnet_id = subnet_id)
+                    new_network.save()
+
+        for (network_id, network) in networks_by_id.items():
+            # If the network disappeared from OS, then reset its network_id to None
+            if (network.network_id is not None) and (network.network_id not in os_networks_by_id):
+                network.network_id = None
+
+            # If no OS object exists, then saving the network will create one
+            if (network.network_id is None):
+                #print "creating OS network for", network.name
+                self.save_network(network)
+            else:
+                pass #print "network", network.name, "has its OS object"
+
 
diff --git a/planetstack/openstack/observer.py b/planetstack/openstack/observer.py
index 6fcb3b4..73bb114 100644
--- a/planetstack/openstack/observer.py
+++ b/planetstack/openstack/observer.py
@@ -1,11 +1,15 @@
 import time
 import traceback
+import commands
+import threading
+
 from datetime import datetime
 from collections import defaultdict
 from core.models import *
 from django.db.models import F, Q
 from openstack.manager import OpenStackManager
-from util.logger import Logger, logging
+from util.logger import Logger, logging, logger
+#from timeout import timeout
 
 
 logger = Logger(logfile='observer.log', level=logging.INFO)
@@ -14,21 +18,98 @@
     
     def __init__(self):
         self.manager = OpenStackManager()
+        # The Condition object that gets signalled by Feefie events
+        self.event_cond = threading.Condition()
+
+    def wait_for_event(self, timeout):
+        self.event_cond.acquire()
+        self.event_cond.wait(timeout)
+        self.event_cond.release()
+        
+    def wake_up(self):
+        logger.info('Wake up routine called. Event cond %r'%self.event_cond)
+        self.event_cond.acquire()
+        self.event_cond.notify()
+        self.event_cond.release()
 
     def run(self):
         if not self.manager.enabled or not self.manager.has_openstack:
             return
         while True:
             try:
+                logger.info('Observer run loop')
                 #self.sync_roles()
-                self.sync_tenants()
-                self.sync_users()
-                self.sync_user_tenant_roles()
-                self.sync_slivers()
-                self.sync_sliver_ips()
-                time.sleep(7)
+
+                logger.info('Calling sync tenants')
+                try:
+                    self.sync_tenants()
+                except:
+                    logger.log_exc("Exception in sync_tenants")
+                    traceback.print_exc()
+
+                logger.info('Calling sync users')
+                try:
+                    self.sync_users()
+                except:
+                    logger.log_exc("Exception in sync_users")
+                    traceback.print_exc()
+
+                logger.info('Calling sync tenant roles')
+                try:
+                    self.sync_user_tenant_roles()
+                except:
+                    logger.log_exc("Exception in sync_users")
+                    traceback.print_exc()
+
+                logger.info('Calling sync slivers')
+                try:
+                    self.sync_slivers()
+                except:
+                    logger.log_exc("Exception in sync slivers")
+                    traceback.print_exc()
+
+                logger.info('Calling sync sliver ips')
+                try:
+                    self.sync_sliver_ips()
+                except:
+                    logger.log_exc("Exception in sync_sliver_ips")
+                    traceback.print_exc()
+
+                logger.info('Calling sync networks')
+                try:
+                    self.sync_networks()
+                except:
+                    logger.log_exc("Exception in sync_networks")
+                    traceback.print_exc()
+
+                logger.info('Calling sync network slivers')
+                try:
+                    self.sync_network_slivers()
+                except:
+                    logger.log_exc("Exception in sync_network_slivers")
+                    traceback.print_exc()
+
+                logger.info('Calling sync external routes')
+                try:
+                    self.sync_external_routes()
+                except:
+                     logger.log_exc("Exception in sync_external_routes")
+                     traceback.print_exc()
+
+                logger.info('Waiting for event')
+                tBeforeWait = time.time()
+                self.wait_for_event(timeout=300)
+
+                # Enforce 5 minutes between wakeups
+                tSleep = 300 - (time.time() - tBeforeWait)
+                if tSleep > 0:
+                    logger.info('Sleeping for %d seconds' % tSleep)
+                    time.sleep(tSleep)
+
+                logger.info('Observer woken up')
             except:
-                traceback.print_exc() 
+                logger.log_exc("Exception in observer run loop")
+                traceback.print_exc()
 
     def sync_roles(self):
         """
@@ -231,7 +312,7 @@
                     # update manager context
                     self.manager.init_caller(sliver.creator, sliver.slice.name)
                     self.manager.save_sliver(sliver)
-                    logger.info("saved sliver: %s %s" % (sliver))
+                    logger.info("saved sliver: %s" % (sliver))
                 except:
                     logger.log_exc("save sliver failed: %s" % sliver) 
 
@@ -243,7 +324,7 @@
             sliver_dict[sliver.instance_id] = sliver
 
         # delete sliver that don't have a sliver record
-        ctx = self.manager.driver.shell.nova_db.ctx 
+        ctx = self.manager.driver.shell.nova_db.ctx
         instances = self.manager.driver.shell.nova_db.instance_get_all(ctx)
         for instance in instances:
             if instance.uuid not in sliver_dict:
@@ -263,7 +344,7 @@
         for sliver in slivers:
             # update connection
             self.manager.init_admin(tenant=sliver.slice.name)
-            servers = self.manager.client.nova.servers.findall(id=sliver.instance_id)
+            servers = self.manager.driver.shell.nova.servers.findall(id=sliver.instance_id)
             if not servers:
                 continue
             server = servers[0]
@@ -273,3 +354,106 @@
             sliver.ip = ips[0]['addr']
             sliver.save()
             logger.info("saved sliver ip: %s %s" % (sliver, ips[0]))
+
+    def sync_external_routes(self):
+        routes = self.manager.driver.get_external_routes() 
+        subnets = self.manager.driver.shell.quantum.list_subnets()['subnets']
+        for subnet in subnets:
+            try:
+                self.manager.driver.add_external_route(subnet, routes)
+            except:
+                logger.log_exc("failed to add external route for subnet %s" % subnet)
+
+    def sync_network_slivers(self):
+        networkSlivers = NetworkSliver.objects.all()
+        networkSlivers_by_id = {}
+        networkSlivers_by_port = {}
+        for networkSliver in networkSlivers:
+            networkSlivers_by_id[networkSliver.id] = networkSliver
+            networkSlivers_by_port[networkSliver.port_id] = networkSliver
+
+        networks = Network.objects.all()
+        networks_by_id = {}
+        for network in networks:
+            networks_by_id[network.network_id] = network
+
+        slivers = Sliver.objects.all()
+        slivers_by_instance_id = {}
+        for sliver in slivers:
+            slivers_by_instance_id[sliver.instance_id] = sliver
+
+        ports = self.manager.driver.shell.quantum.list_ports()["ports"]
+        for port in ports:
+            if port["id"] in networkSlivers_by_port:
+                # we already have it
+                print "already accounted for port", port["id"]
+                continue
+
+            if port["device_owner"] != "compute:nova":
+                # we only want the ports that connect to instances
+                continue
+
+            network = networks_by_id.get(port['network_id'], None)
+            if not network:
+                #print "no network for port", port["id"], "network", port["network_id"]
+                continue
+
+            sliver = slivers_by_instance_id.get(port['device_id'], None)
+            if not sliver:
+                print "no sliver for port", port["id"], "device_id", port['device_id']
+                continue
+
+            if network.template.sharedNetworkId is not None:
+                # If it's a shared network template, then more than one network
+                # object maps to the quantum network. We have to do a whole bunch
+                # of extra work to find the right one.
+                networks = network.template.network_set.all()
+                network = None
+                for candidate_network in networks:
+                    if (candidate_network.owner == sliver.slice):
+                        print "found network", candidate_network
+                        network = candidate_network
+
+                if not network:
+                    print "failed to find the correct network for a shared template for port", port["id"], "network", port["network_id"]
+                    continue
+
+            if not port["fixed_ips"]:
+                print "port", port["id"], "has no fixed_ips"
+                continue
+
+#            print "XXX", port
+
+            ns = NetworkSliver(network=network,
+                               sliver=sliver,
+                               ip=port["fixed_ips"][0]["ip_address"],
+                               port_id=port["id"])
+            ns.save()
+
+    def sync_networks(self):
+        """
+        save all networks where enacted < updated or enacted == None. Remove networks that
+        no don't exist in openstack db if they have an enacted time (enacted != None).
+        """
+        # get all users that need to be synced (enacted < updated or enacted is None)
+        pending_networks = Network.objects.filter(Q(enacted__lt=F('updated')) | Q(enacted=None))
+        for network in pending_networks:
+            if network.owner and network.owner.creator:
+                try:
+                    # update manager context
+                    self.manager.init_caller(network.owner.creator, network.owner.name)
+                    self.manager.save_network(network)
+                    logger.info("saved network: %s" % (network))
+                except:
+                    logger.log_exc("save network failed: %s" % network)
+
+        # get all networks where enacted != null. We can assume these users
+        # have previously been synced and need to be checed for deletion.
+        networks = Network.objects.filter(enacted__isnull=False)
+        network_dict = {}
+        for network in networks:
+            network_dict[network.network_id] = network
+
+        # TODO: delete Network objects if quantum network doesn't exist
+        #       (need to write self.manager.driver.shell.quantum_db)
+
diff --git a/planetstack/openstack/openstack-db-cleanup.sh b/planetstack/openstack/openstack-db-cleanup.sh
new file mode 100755
index 0000000..9baca6e
--- /dev/null
+++ b/planetstack/openstack/openstack-db-cleanup.sh
@@ -0,0 +1,16 @@
+#! /bin/bash
+
+# to install
+#    chmod 0755 /opt/planetstack/openstack/openstack-db-cleanup.sh
+#    ln -s /opt/planetstack/openstack/openstack-db-cleanup.sh /etc/cron.daily/openstack-db-cleanup.cron
+
+mkdir -p /opt/planetstack/ovs-backups
+BACKUP_NAME=/opt/planetstack/ovs-backups/backup-`date "+%Y-%M-%d"`.sql
+mysqldump --create-options --routines --triggers --databases keystone ovs_quantum nova glance cinder > $BACKUP_NAME
+gzip $BACKUP_NAME
+
+mysql keystone -e "DELETE FROM token WHERE NOT DATE_SUB(CURDATE(),INTERVAL 2 DAY) <= expires;"
+mysqlcheck --optimize --databases keystone ovs_quantum nova glance cinder
+
+date >> /var/log/openstack-db-cleanup.log
+mysql keystone -e "select count(*) from token;" >> /var/log/openstack-db-cleanup.log
diff --git a/planetstack/planetstack/urls.py b/planetstack/planetstack/urls.py
index 30eed05..66f376a 100644
--- a/planetstack/planetstack/urls.py
+++ b/planetstack/planetstack/urls.py
@@ -2,17 +2,17 @@
 
 # Uncomment the next two lines to enable the admin:
 from django.contrib import admin
-from core.views.roles import RoleListCreate, RoleRetrieveUpdateDestroy
-from core.views.sites import SiteListCreate, SiteRetrieveUpdateDestroy
-from core.views.site_privileges import SitePrivilegeListCreate, SitePrivilegeRetrieveUpdateDestroy
-from core.views.users import UserListCreate, UserRetrieveUpdateDestroy
-from core.views.slices import SliceListCreate, SliceRetrieveUpdateDestroy
-from core.views.slice_memberships import SliceMembershipListCreate, SliceMembershipRetrieveUpdateDestroy
-from core.views.slivers import SliverListCreate, SliverRetrieveUpdateDestroy
-from core.views.deployment_networks import DeploymentListCreate, DeploymentRetrieveUpdateDestroy
-from core.views.images import ImageListCreate, ImageRetrieveUpdateDestroy
-from core.views.nodes import NodeListCreate, NodeRetrieveUpdateDestroy
-from core.models import Site
+from core.views.roles import RoleList, RoleDetail
+from core.views.sites import SiteList, SiteDetail
+from core.views.site_privileges import SitePrivilegeList, SitePrivilegeDetail
+from core.views.users import UserList, UserDetail
+from core.views.slices import SliceList, SliceDetail
+from core.views.slice_memberships import SliceMembershipList, SliceMembershipDetail
+from core.views.slivers import SliverList, SliverDetail
+from core.views.deployments import DeploymentList, DeploymentDetail
+from core.views.images import ImageList, ImageDetail
+from core.views.nodes import NodeList, NodeDetail
+from core.models import *
 from core.api_root import api_root
 from rest_framework import generics
 
@@ -31,35 +31,36 @@
 
     url(r'^plstackapi/$', api_root),
     
-    url(r'^plstackapi/roles/$', RoleListCreate.as_view(), name='role-list'),
-    url(r'^plstackapi/roles/(?P<pk>[a-zA-Z0-9]+)/$', RoleRetrieveUpdateDestroy.as_view(), name='role-detail'),
+    url(r'^plstackapi/roles/$', RoleList.as_view(), name='role-list'),
+    url(r'^plstackapi/roles/(?P<pk>[a-zA-Z0-9]+)/$', RoleDetail.as_view(), name='role-detail'),
 
-    url(r'^plstackapi/users/$', UserListCreate.as_view(), name='user-list'),
-    url(r'^plstackapi/users/(?P<pk>[a-zA-Z0-9_\-]+)/$', UserRetrieveUpdateDestroy.as_view(), name='user-detail'),
+    url(r'^plstackapi/users/$', UserList.as_view(), name='user-list'),
+    url(r'^plstackapi/users/(?P<pk>[a-zA-Z0-9_\-]+)/$', UserDetail.as_view(), name='user-detail'),
 
-    url(r'^plstackapi/sites/$', SiteListCreate.as_view(), name='site-list'),
-    url(r'^plstackapi/sites/(?P<pk>[a-zA-Z0-9_\-]+)/$', SiteRetrieveUpdateDestroy.as_view(), name='site-detail'),
+    url(r'^plstackapi/sites/$', SiteList.as_view(), name='site-list'),
+    url(r'^plstackapi/sites/(?P<pk>[a-zA-Z0-9_\-]+)/$', SiteDetail.as_view(), name='site-detail'),
 
-    url(r'^plstackapi/site_privileges/$', SitePrivilegeListCreate.as_view(), name='siteprivilege-list'),
-    url(r'^plstackapi/site_privileges/(?P<pk>[a-zA-Z0-9_]+)/$', SitePrivilegeRetrieveUpdateDestroy.as_view(), name='siteprivilege-detail'),
+    url(r'^plstackapi/site_privileges/$', SitePrivilegeList.as_view(), name='siteprivilege-list'),
+    url(r'^plstackapi/site_privileges/(?P<pk>[a-zA-Z0-9_]+)/$', SitePrivilegeDetail.as_view(), name='siteprivilege-detail'),
+  
+    url(r'^plstackapi/slices/$', SliceList.as_view(), name='slice-list'),
 
-    url(r'^plstackapi/slices/$', SliceListCreate.as_view(), name='slice-list'),
-    url(r'^plstackapi/slices/(?P<pk>[a-zA-Z0-9_\-]+)/$', SliceRetrieveUpdateDestroy.as_view(), name='slice-detail'),
+    url(r'^plstackapi/slices/(?P<pk>[a-zA-Z0-9_\-]+)/$', SliceDetail.as_view(), name='slice-detail'),
 
-    url(r'^plstackapi/slice_memberships/$', SliceMembershipListCreate.as_view(), name='slice_membership-list'),
-    url(r'^plstackapi/slice_memberships/(?P<pk>[0-9]+)/$', SliceMembershipRetrieveUpdateDestroy.as_view(), name='slice_membership-detail'),
+    url(r'^plstackapi/slice_memberships/$', SliceMembershipList.as_view(), name='slice-membership-list'),
+    url(r'^plstackapi/slice_memberships/(?P<pk>[0-9]+)/$', SliceMembershipDetail.as_view(), name='slice-membership-detail'),
     
-    url(r'^plstackapi/slivers/$', SliverListCreate.as_view(), name='sliver-list'),
-    url(r'^plstackapi/slivers/(?P<pk>[a-zA-Z0-9_\-]+)/$', SliverRetrieveUpdateDestroy.as_view(), name='sliver-detail'),
+    url(r'^plstackapi/slivers/$', SliverList.as_view(), name='sliver-list'),
+    url(r'^plstackapi/slivers/(?P<pk>[a-zA-Z0-9_\-]+)/$', SliverDetail.as_view(), name='sliver-detail'),
 
-    url(r'^plstackapi/nodes/$', NodeListCreate.as_view(), name='node-list'),
-    url(r'^plstackapi/nodes/(?P<pk>[a-zA-Z0-9_\-]+)/$', NodeRetrieveUpdateDestroy.as_view(), name='node-detail'),
+    url(r'^plstackapi/nodes/$', NodeList.as_view(), name='node-list'),
+    url(r'^plstackapi/nodes/(?P<pk>[a-zA-Z0-9_\-]+)/$', NodeDetail.as_view(), name='node-detail'),
     
-    url(r'^plstackapi/deploymentnetworks/$', DeploymentListCreate.as_view(), name='deploymentnetwork-list'),
-    url(r'^plstackapi/deploymentnetworks/(?P<pk>[a-zA-Z0-9\-]+)/$', DeploymentRetrieveUpdateDestroy.as_view(), name='deploymentnetwork-detail'),
+    url(r'^plstackapi/deployments/$', DeploymentList.as_view(), name='deployment-list'),
+    url(r'^plstackapi/deployments/(?P<pk>[a-zA-Z0-9\-]+)/$', DeploymentDetail.as_view(), name='deployment-detail'),
 
-    url(r'^plstackapi/images/$', ImageListCreate.as_view(), name='image-list'),
-    url(r'^plstackapi/images/(?P<pk>[a-zA-Z0-9_\-]+)/$', ImageRetrieveUpdateDestroy.as_view(), name='image-detail'),
+    url(r'^plstackapi/images/$', ImageList.as_view(), name='image-list'),
+    url(r'^plstackapi/images/(?P<pk>[a-zA-Z0-9_\-]+)/$', ImageDetail.as_view(), name='image-detail'),
 
     #Adding in rest_framework urls
     url(r'^plstackapi/', include('rest_framework.urls', namespace='rest_framework')),