dynamic home view with customization
Scott Baker [Tue, 20 May 2014 00:55:56 +0000 (17:55 -0700)]
planetstack/core/admin.py
planetstack/core/models/__init__.py
planetstack/core/models/dashboard.py [new file with mode: 0644]
planetstack/core/models/user.py
planetstack/core/plus/sites.py
planetstack/core/plus/views.py
planetstack/core/static/planetstack.css
planetstack/templates/admin/dashboard/customize.html [new file with mode: 0644]

index 3b87dc8..f054e3f 100644 (file)
@@ -874,6 +874,12 @@ class UserChangeForm(forms.ModelForm):
         # field does not have access to the initial value
         return self.initial["password"]
 
+class UserDashboardViewInline(PlStackTabularInline):
+    model = UserDashboardView
+    extra = 0
+    suit_classes = 'suit-tab suit-tab-dashboards'
+    fields = ['user', 'dashboardView', 'order']
+
 class UserAdmin(UserAdmin):
     class Meta:
         app_label = "core"
@@ -888,7 +894,7 @@ class UserAdmin(UserAdmin):
     list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
     #list_display = ('email', 'username','firstname', 'lastname', 'is_admin', 'last_login')
     list_filter = ('site',)
-    inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline]
+    inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
 
     fieldListLoginDetails = ['email','site','password','is_readonly','is_amin','public_key']
     fieldListContactInfo = ['firstname','lastname','phone','timezone']
@@ -896,6 +902,7 @@ class UserAdmin(UserAdmin):
     fieldsets = (
         ('Login Details', {'fields': ['email', 'site','password', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
         ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
+        #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
         #('Important dates', {'fields': ('last_login',)}),
     )
     add_fieldsets = (
@@ -911,7 +918,12 @@ class UserAdmin(UserAdmin):
     user_readonly_fields = fieldListLoginDetails
     user_readonly_inlines = [SlicePrivilegeROInline,SitePrivilegeROInline,DeploymentPrivilegeROInline]
 
-    suit_form_tabs =(('general','Login Details'),('contact','Contact Information'),('sliceprivileges','Slice Privileges'),('siteprivileges','Site Privileges'),('deploymentprivileges','Deployment Privileges'))
+    suit_form_tabs =(('general','Login Details'),
+                     ('contact','Contact Information'),
+                     ('sliceprivileges','Slice Privileges'),
+                     ('siteprivileges','Site Privileges'),
+                     ('deploymentprivileges','Deployment Privileges'),
+                     ('dashboards','Dashboard Views'))
 
     def formfield_for_foreignkey(self, db_field, request, **kwargs):
         if db_field.name == 'site':
@@ -956,7 +968,13 @@ class UserAdmin(UserAdmin):
     def queryset(self, request):
         return User.select_by_user(request.user)
 
+class DashboardViewAdmin(PlanetStackBaseAdmin):
+    fieldsets = [('Dashboard View Details',
+                   {'fields': ['name', 'url'],
+                    'classes': ['suit-tab suit-tab-general']})
+               ]
 
+    suit_form_tabs =(('general','Dashboard View Details'),)
 
 class ServiceResourceROInline(ReadOnlyTabularInline):
     model = ServiceResource
@@ -1378,4 +1396,5 @@ if True:
     #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
     admin.site.register(Sliver, SliverAdmin)
     admin.site.register(Image, ImageAdmin)
+    admin.site.register(DashboardView, DashboardViewAdmin)
 
index bf0015a..5dda2ed 100644 (file)
@@ -8,7 +8,8 @@ from .tag import Tag
 from .role import Role
 #from .deployment import Deployment
 from .site import Site,Deployment, DeploymentRole, DeploymentPrivilege, SiteDeployments
-from .user import User, UserDeployments
+from .dashboard import DashboardView
+from .user import User, UserDeployments, UserDashboardView
 from .serviceclass import ServiceClass
 from .slice import Slice, SliceDeployments
 from .site import SitePrivilege, SiteDeployments
@@ -29,3 +30,4 @@ from .reservation import ReservedResource
 from .reservation import Reservation
 from .network import Network, NetworkParameterType, NetworkParameter, NetworkSliver, NetworkTemplate, Router, NetworkSlice, NetworkDeployments
 from .billing import Account, Invoice, Charge, UsableObject, Payment
+
diff --git a/planetstack/core/models/dashboard.py b/planetstack/core/models/dashboard.py
new file mode 100644 (file)
index 0000000..aa79f84
--- /dev/null
@@ -0,0 +1,11 @@
+import os
+from django.db import models
+from core.models import PlCoreBase
+from django.contrib.contenttypes import generic
+
+class DashboardView(PlCoreBase):
+    name = models.CharField(max_length=200, unique=True, help_text="Name of the View")
+    url = models.CharField(max_length=1024, help_text="URL of Dashboard")
+
+    def __unicode__(self):  return u'%s' % (self.name)
+
index c4e06e0..6e7eef6 100644 (file)
@@ -3,10 +3,11 @@ import datetime
 from collections import defaultdict
 from django.db import models
 from django.db.models import F, Q
-from core.models import PlCoreBase,Site
+from core.models import PlCoreBase,Site, DashboardView
 from core.models.deployment import Deployment
 from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
 from timezones.fields import TimeZoneField
+from operator import itemgetter, attrgetter
 
 # Create your models here.
 class UserManager(BaseUserManager):
@@ -77,6 +78,8 @@ class User(AbstractBaseUser):
 
     timezone = TimeZoneField()
 
+    dashboards = models.ManyToManyField('DashboardView', through='UserDashboardView', blank=True)
+
     objects = UserManager()
 
     USERNAME_FIELD = 'email'
@@ -113,6 +116,20 @@ class User(AbstractBaseUser):
     def is_superuser(self):
         return False
 
+    def get_dashboards(self):
+        DEFAULT_DASHBOARDS=["Tenant"]
+
+        dashboards = sorted(list(self.dashboardViews.all()), key=attrgetter('order'))
+        dashboards = [x.dashboardView for x in dashboards]
+
+        if not dashboards:
+            for dashboardName in DEFAULT_DASHBOARDS:
+                dbv = DashboardView.objects.filter(name=dashboardName)
+                if dbv:
+                    dashboards.append(dbv[0])
+
+        return dashboards
+
 #    def get_roles(self):
 #        from core.models.site import SitePrivilege
 #        from core.models.slice import SliceMembership
@@ -163,4 +180,9 @@ class UserDeployments(PlCoreBase):
         else:
             users = Users.select_by_user(user)
             qs = Usereployments.objects.filter(user__in=slices)
-        return qs 
+        return qs
+
+class UserDashboardView(PlCoreBase):
+     user = models.ForeignKey(User, related_name="dashboardViews")
+     dashboardView = models.ForeignKey(DashboardView, related_name="dashboardViews")
+     order = models.IntegerField(default=0)
index b496481..66c5d00 100644 (file)
@@ -12,17 +12,19 @@ class AdminMixin(object):
     def get_urls(self):
         """Add our dashboard view to the admin urlconf. Deleted the default index."""
         from django.conf.urls import patterns, url
-        from views import DashboardView, DashboardWelcomeView, DashboardAjaxView, SimulatorView, DashboardSummaryAjaxView, DashboardAddOrRemoveSliverView, DashboardUserSiteView, DashboardAnalyticsAjaxView, TenantViewData,TenantCreateSlice, TenantAddOrRemoveSliverView, TenantPickSitesView, TenantDeleteSliceView,TenantUpdateSlice
+        from views import DashboardCustomize, DashboardDynamicView, DashboardWelcomeView, DashboardAjaxView, SimulatorView, DashboardSummaryAjaxView, DashboardAddOrRemoveSliverView, DashboardUserSiteView, DashboardAnalyticsAjaxView, TenantViewData,TenantCreateSlice, TenantAddOrRemoveSliverView, TenantPickSitesView, TenantDeleteSliceView,TenantUpdateSlice
 
         urls = super(AdminMixin, self).get_urls()
         del urls[0]
         custom_url = patterns('',
-               url(r'^$', self.admin_view(DashboardWelcomeView.as_view()),
+               url(r'^$', self.admin_view(DashboardDynamicView.as_view()),
                     name="index"),
                url(r'^test/', self.admin_view(DashboardUserSiteView.as_view()),
                     name="test"),
-               url(r'^dashboard/(?P<name>\w+)/$', self.admin_view(DashboardView.as_view()),
+               url(r'^dashboard/(?P<name>\w+)/$', self.admin_view(DashboardDynamicView.as_view()),
                     name="dashboard"),
+              url(r'^customize/$', self.admin_view(DashboardCustomize.as_view()),
+                    name="customize"),
                url(r'^hpcdashuserslices/', self.admin_view(DashboardUserSiteView.as_view()),
                     name="hpcdashuserslices"),
                url(r'^hpcdashboard/', self.admin_view(DashboardAjaxView.as_view()),        # DEPRECATED
index 451ee6d..142911b 100644 (file)
@@ -17,6 +17,7 @@ from django.http import HttpResponse, HttpResponseServerError
 from django.core import urlresolvers
 from django.contrib.gis.geoip import GeoIP
 from ipware.ip import get_ip
+from operator import itemgetter, attrgetter
 import traceback
 import socket
 
@@ -34,15 +35,10 @@ class DashboardWelcomeView(TemplateView):
 
     def get(self, request, *args, **kwargs):
         context = self.get_context_data(**kwargs)
-        userDetails = getUserSliceInfo(request.user)
-        #context['site'] = userDetails['site']
-
-        context['userSliceInfo'] = userDetails['userSliceInfo']
-        context['cdnData'] = userDetails['cdnData']
-        context['cdnContentProviders'] = userDetails['cdnContentProviders']
+        context = getDashboardContext(request.user, context)
         return self.render_to_response(context=context)
 
-class DashboardView(TemplateView):
+class DashboardDynamicView(TemplateView):
     head_template = r"""{% extends "admin/dashboard/dashboard_base.html" %}
        {% load admin_static %}
        {% block content %}
@@ -50,25 +46,70 @@ class DashboardView(TemplateView):
 
     tail_template = r"{% endblock %}"
 
-    def get(self, request, name="hpc_historical", *args, **kwargs):
+    def get(self, request, name="root", *args, **kwargs):
         context = self.get_context_data(**kwargs)
+        context = getDashboardContext(request.user, context)
+
+        if name=="root":
+            return self.multiDashboardView(request, context)
+        else:
+            return self.singleDashboardView(request, name, context)
 
+    def readDashboard(self, fn):
+        try:
+            template= open("/opt/planetstack/templates/admin/dashboard/%s.html" % fn, "r").read()
+            if (fn=="tenant"):
+                template = '<div id="tabs-5"></div>' + template
+            return template
+        except:
+            return "failed to open %s" % fn
+
+    def multiDashboardView(self, request, context):
         head_template = self.head_template
         tail_template = self.tail_template
 
-        if (name=="tenant"):
-            # quick fix for tenant view
-            head_template = head_template + '<div id="tabs-5"></div>'
+        body = """
+         <div id="hometabs" >
+         <ul id="suit_form_tabs" class="nav nav-tabs nav-tabs-suit" data-tab-prefix="suit-tab">
+        """
+
+        dashboards = request.user.get_dashboards()
+
+        # customize is a special dashboard they always get
+        customize = DashboardView.objects.filter(name="Customize")
+        if customize:
+            dashboards.append(customize[0])
+
+        for i,view in enumerate(dashboards):
+            body = body + '<li><a href="#dashtab-%d">%s</a></li>\n' % (i, view.name)
 
+        body = body + "</ul>\n"
 
-        t = template.Template(head_template + open("/opt/planetstack/templates/admin/dashboard/%s.html" % name, "r").read() + self.tail_template)
+        for i,view in enumerate(dashboards):
+            url = view.url
+            body = body + '<div id="dashtab-%d">\n' % i
+            if url.startswith("template:"):
+                fn = url[9:]
+                body = body + self.readDashboard(fn)
+            body = body + '</div>\n'
+
+        body=body+"</div>\n"
+
+        t = template.Template(head_template + body + self.tail_template)
+
+        response_kwargs = {}
+        response_kwargs.setdefault('content_type', self.content_type)
+        return self.response_class(\r
+            request = request,\r
+            template = t,\r
+            context = context,\r
+            **response_kwargs)
 
-        userDetails = getUserSliceInfo(request.user)
-        #context['site'] = userDetails['site']
+    def singleDashboardView(self, request, name, context):
+        head_template = self.head_template
+        tail_template = self.tail_template
 
-        context['userSliceInfo'] = userDetails['userSliceInfo']
-        context['cdnData'] = userDetails['cdnData']
-        context['cdnContentProviders'] = userDetails['cdnContentProviders']
+        t = template.Template(head_template + self.readDashboard(fn) + self.tail_template)
 
         response_kwargs = {}
         response_kwargs.setdefault('content_type', self.content_type)
@@ -78,18 +119,36 @@ class DashboardView(TemplateView):
             context = context,\r
             **response_kwargs)
 
-def getUserSliceInfo(user, tableFormat = False):
-        userDetails = {}
+def getDashboardContext(user, context={}, tableFormat = False):
+        context = {}
 
         userSliceData = getSliceInfo(user)
         if (tableFormat):
-#            pprint("*******      GET USER SLICE INFO")
-            userDetails['userSliceInfo'] = userSliceTableFormatter(userSliceData)
+            context['userSliceInfo'] = userSliceTableFormatter(userSliceData)
         else:
-            userDetails['userSliceInfo'] = userSliceData
-        userDetails['cdnData'] = getCDNOperatorData(wait=False)
-        userDetails['cdnContentProviders'] = getCDNContentProviderData()
-        return userDetails
+            context['userSliceInfo'] = userSliceData
+        context['cdnData'] = getCDNOperatorData(wait=False)
+        context['cdnContentProviders'] = getCDNContentProviderData()
+
+        (dashboards, unusedDashboards)= getDashboards(user)
+        unusedDashboards=[x for x in unusedDashboards if x!="Customize"]
+        context['dashboards'] = dashboards
+        context['unusedDashboards'] = unusedDashboards
+
+        return context
+
+def getDashboards(user):
+    #dashboards = sorted(list(user.dashboardViews.all()), key=attrgetter('order'))
+    dashboards = user.get_dashboards()
+
+    dashboard_names = [d.name for d in dashboards]
+
+    unused_dashboard_names = []
+    for dashboardView in DashboardView.objects.all():
+        if not dashboardView.name in dashboard_names:
+            unused_dashboard_names.append(dashboardView.name)
+
+    return (dashboard_names, unused_dashboard_names)
 
 class TenantCreateSlice(View):
     def post(self, request, *args, **kwargs):
@@ -416,7 +475,7 @@ class SimulatorView(View):
 
 class DashboardUserSiteView(View):
     def get(self, request, **kwargs):
-        return HttpResponse(json.dumps(getUserSliceInfo(request.user, True)), mimetype='application/javascript')
+        return HttpResponse(json.dumps(getDashboardContext(request.user, tableFormat=True)), mimetype='application/javascript')
 
 class TenantViewData(View):
     def get(self, request, **kwargs):
@@ -654,7 +713,7 @@ class DashboardAnalyticsAjaxView(View):
         if (name == "hpcSummary"):
             return HttpResponse(json.dumps(hpc_wizard.get_hpc_wizard().get_summary_for_view()), mimetype='application/javascript')
         elif (name == "hpcUserSite"):
-            return HttpResponse(json.dumps(getUserSliceInfo(request.user, True)), mimetype='application/javascript')
+            return HttpResponse(json.dumps(getDashboardContext(request.user, tableFormat=True)), mimetype='application/javascript')
         elif (name == "hpcMap"):
             return HttpResponse(json.dumps(getCDNOperatorData(True)), mimetype='application/javascript')
         elif (name == "bigquery"):
@@ -663,3 +722,21 @@ class DashboardAnalyticsAjaxView(View):
         else:
             return HttpResponse(json.dumps("Unknown"), mimetype='application/javascript')
 
+class DashboardCustomize(View):
+    def post(self, request, *args, **kwargs):\r
+        dashboards = request.POST.get("dashboards", None)\r
+        if not dashboards:\r
+            return HttpResponse("no data")\r
+\r
+        dashboards = [x.strip() for x in dashboards.split(",")]\r
+\r
+        dashboards = [DashboardView.objects.get(name=x) for x in dashboards]\r
+\r
+        request.user.dashboardViews.all().delete()\r
+\r
+        for i,dashboard in enumerate(dashboards):\r
+            udbv = UserDashboardView(user=request.user, dashboardView=dashboard, order=i)\r
+            udbv.save()\r
+\r
+        return HttpResponse("updated")\r
+
index 39102be..234261c 100644 (file)
@@ -1141,4 +1141,12 @@ display:none;
 
 #private-vol{
 margin-right: 15% !important;
-}
\ No newline at end of file
+}\r
+\r
+.customize_row {\r
+  display: table;\r
+}\r
+.customize_column {\r
+  display: table-cell;\r
+  padding: 10px;\r
+}\r
diff --git a/planetstack/templates/admin/dashboard/customize.html b/planetstack/templates/admin/dashboard/customize.html
new file mode 100644 (file)
index 0000000..3f1d2c6
--- /dev/null
@@ -0,0 +1,85 @@
+<form>
+    <div class="customize_row">\r
+    <div class="customize_column">\r
+    <div>Available Dashboard Views</div>\r
+    <select name="selectfrom" id="select-from" multiple size="5">\r
+        {% for cp in unusedDashboards %}\r
+           <option value="{{ cp }}">{{ cp }}</option>
+        {% endfor %}\r
+    </select>\r
+    </div>\r
+    <div class="customize_column">\r
+    <br>\r
+    <div class="btn btn-success" id="btn-add">Add &raquo;</div><br><br>\r
+    <div class="btn btn-success" id="btn-remove">&laquo; Remove</div>\r
+    </div>\r
+    <div class="customize_column">\r
+    <div>Selected Dashboard Views</div>\r
+    <select name="selectto" id="select-to" multiple size="5">\r
+        {% for cp in dashboards %}\r
+           <option value="{{ cp }}">{{ cp }}</option>
+        {% endfor %}\r
+    </select>\r
+    <br>\r
+    <div class="btn btn-high btn-info" id="btn-save">Save</div>\r
+    </div>\r
+    <div class="customize_column">\r
+    <br>\r
+    <div class="btn btn-success" id="btn-up">Up</div><br><br>\r
+    <div class="btn btn-success" id="btn-down">Down</div>\r
+    </div>\r
+    </div>\r
+</form>\r
+
+<script>
+$(document).ready(function() {
+    $('#btn-add').click(function(){\r
+        $('#select-from option:selected').each( function() {\r
+                $('#select-to').append("<option value='"+$(this).val()+"'>"+$(this).text()+"</option>");\r
+            $(this).remove();\r
+        });\r
+    });\r
+    $('#btn-remove').click(function(){\r
+        $('#select-to option:selected').each( function() {\r
+            $('#select-from').append("<option value='"+$(this).val()+"'>"+$(this).text()+"</option>");\r
+            $(this).remove();\r
+        });\r
+    });\r
+    $('#btn-up').bind('click', function() {\r
+        $('#select-to option:selected').each( function() {\r
+            var newPos = $('#select-to option').index(this) - 1;\r
+            if (newPos > -1) {\r
+                $('#select-to option').eq(newPos).before("<option value='"+$(this).val()+"' selected='selected'>"+$(this).text()+"</option>");\r
+                $(this).remove();\r
+            }\r
+        });\r
+    });\r
+    $('#btn-down').bind('click', function() {\r
+        var countOptions = $('#select-to option').size();\r
+        $('#select-to option:selected').each( function() {\r
+            var newPos = $('#select-to option').index(this) + 1;\r
+            if (newPos < countOptions) {\r
+                $('#select-to option').eq(newPos).after("<option value='"+$(this).val()+"' selected='selected'>"+$(this).text()+"</option>");\r
+                $(this).remove();\r
+            }\r
+        });\r
+    });\r
+    $('#btn-save').bind('click', function() {\r
+         var items=[];\r
+         $("#select-to option").each(function() { items.push($(this).val()); });\r
+         $.ajax({\r
+                url: '/customize/',
+                dataType: 'json',
+                data: {
+                        dashboards: items.join(","),
+                       csrfmiddlewaretoken: "{{ csrf_token }}" // < here
+                },
+                type: 'POST',
+                complete: function () {
+                        location.reload();
+                }
+        });\r
+    });
+});
+</script>
+