Django-suit, add in Roles for specific classes site, slice, deployment, planetstack...
Siobhan Tully [Tue, 3 Sep 2013 16:59:24 +0000 (12:59 -0400)]
22 files changed:
planetstack/core/admin.py
planetstack/core/fixtures/initial_data.json
planetstack/core/models/__init__.py
planetstack/core/models/deployment.py
planetstack/core/models/planetstack.py [new file with mode: 0644]
planetstack/core/models/role.py
planetstack/core/models/site.py
planetstack/core/models/slice.py
planetstack/core/models/user.py
planetstack/core/serializers.py
planetstack/core/static/planetstack.css
planetstack/core/views/slice_memberships.py [deleted file]
planetstack/core/views/slice_privileges.py [new file with mode: 0644]
planetstack/hpc/__init__.py [new file with mode: 0644]
planetstack/hpc/admin.py [new file with mode: 0644]
planetstack/hpc/models.py [new file with mode: 0644]
planetstack/hpc/tests.py [new file with mode: 0644]
planetstack/hpc/views.py [new file with mode: 0644]
planetstack/planetstack/settings.py
planetstack/planetstack/urls.py
planetstack/templates/admin/base.html [new file with mode: 0644]
planetstack/templates/admin/base_site.html

index a1a21d6..730937a 100644 (file)
@@ -12,12 +12,19 @@ from django.contrib.auth.forms import ReadOnlyPasswordHashField
 from django.contrib.auth.signals import user_logged_in
 from django.utils import timezone
 from django.contrib.contenttypes import generic
+from suit.widgets import LinkedSelect
 
 import django_evolution 
 
 class PlStackTabularInline(admin.TabularInline):
     exclude = ['enacted']
 
+class ReservationInline(PlStackTabularInline):
+    model = Reservation
+    extra = 0
+    suit_classes = 'suit-tab suit-tab-reservations'
+
+
 class ReadonlyTabularInline(PlStackTabularInline):
     can_delete = False
     extra = 0
@@ -34,10 +41,27 @@ class ReadonlyTabularInline(PlStackTabularInline):
     def has_add_permission(self, request):
         return False
 
+class UserMembershipInline(generic.GenericTabularInline):
+    model = Member
+    exclude = ['enacted']
+    extra = 1
+    suit_classes = 'suit-tab suit-tab-membership'
+
+    def queryset(self, request):
+        qs = super(UserMembershipInline, self).queryset(request)
+        return qs.filter(user=request.user)
+        
+class MemberInline(generic.GenericTabularInline):
+    model = Member
+    exclude = ['enacted']
+    extra = 1
+    suit_classes = 'suit-tab suit-tab-members'
+
 class TagInline(generic.GenericTabularInline):
     model = Tag
     exclude = ['enacted']
-    extra = 1
+    extra = 0
+    suit_classes = 'suit-tab suit-tab-tags'
 
 class SliverInline(PlStackTabularInline):
     model = Sliver
@@ -45,32 +69,51 @@ class SliverInline(PlStackTabularInline):
     extra = 0
     #readonly_fields = ['ip', 'instance_name', 'image']
     readonly_fields = ['ip', 'instance_name']
+    suit_classes = 'suit-tab suit-tab-slivers'
     
 
 class SiteInline(PlStackTabularInline):
     model = Site
     extra = 0
+    suit_classes = 'suit-tab suit-tab-sites'
 
 class UserInline(PlStackTabularInline):
     model = User
     fields = ['email', 'firstname', 'lastname']
     extra = 0
+    suit_classes = 'suit-tab suit-tab-users'
 
 class SliceInline(PlStackTabularInline):
     model = Slice
+    fields = ['name','enabled','description','slice_url']
     extra = 0
+    suit_classes = 'suit-tab suit-tab-slices'
+
 
 class RoleInline(PlStackTabularInline):
     model = Role
     extra = 0 
+    suit_classes = 'suit-tab suit-tab-roles'
 
 class NodeInline(PlStackTabularInline):
     model = Node
     extra = 0
+    suit_classes = 'suit-tab suit-tab-nodes'
+
+class SlicePrivilegeInline(PlStackTabularInline):
+    model = SlicePrivilege
+    extra = 0
+    suit_classes = 'suit-tab suit-tab-sliceprivileges'
+
+class DeploymentPrivilegeInline(PlStackTabularInline):
+    model = DeploymentPrivilege
+    extra = 0
+    suit_classes = 'suit-tab suit-tab-deploymentprivileges'
 
 class SitePrivilegeInline(PlStackTabularInline):
     model = SitePrivilege
     extra = 0
+    suit_classes = 'suit-tab suit-tab-siteprivileges'
 
     def formfield_for_foreignkey(self, db_field, request, **kwargs):
         if db_field.name == 'site':
@@ -94,10 +137,17 @@ class SitePrivilegeInline(PlStackTabularInline):
                 kwargs['queryset'] = users
         return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
 
-class SliceMembershipInline(PlStackTabularInline):
-    model = SliceMembership
+class SitePrivilegeInline(PlStackTabularInline):
+    model = SitePrivilege
+    suit_classes = 'suit-tab suit-tab-siteprivileges'
+    extra = 0
+    fields = ('user', 'site','role')
+
+class SlicePrivilegeInline(PlStackTabularInline):
+    model = SlicePrivilege
+    suit_classes = 'suit-tab suit-tab-sliceprivileges'
     extra = 0
-    fields = ('user', 'role')
+    fields = ('user', 'slice','role')
 
     def formfield_for_foreignkey(self, db_field, request, **kwargs):
         if db_field.name == 'slice':
@@ -119,7 +169,7 @@ class SliceMembershipInline(PlStackTabularInline):
                 users = User.objects.filter(email__in=emails) 
                 kwargs['queryset'] = list(users)
 
-        return super(SliceMembershipInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
+        return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
 
 class SliceTagInline(PlStackTabularInline):
     model = SliceTag
@@ -137,11 +187,38 @@ class PlanetStackBaseAdmin(admin.ModelAdmin):
     save_on_top = False
     exclude = ['enacted']
 
+#class RoleMemberForm(forms.ModelForm):
+#    request=None
+#    member=forms.ModelChoiceField(queryset=Member.objects.all()) #first get all
+#
+#    def __init__(self,fata=None,files=None,auto_id='id_%s',prefix=None,initial=None,error_class=ErrorList,label_suffix=':',empty_permitted=False,instance=None):
+#        super(RoleMemberForm,self).__init__data,files,auto_id,prefix,initial,error_class,label_suffix,empty_permitted,instance)
+#
+#        self.fields["member"].queryset = member.objects.filter(
+
+class RoleMemberInline (admin.StackedInline):
+    model = Member
+#    form = RoleMemberForm
+    
+    def get_formset(self,request,obj=None, **kwargs):
+        self.form.request=request
+        return super(RoleMemberInline, self).get_formset(request, obj, **kwargs)
+
+class SliceRoleAdmin(PlanetStackBaseAdmin):
+    model = SliceRole
+    pass
+
+class SiteRoleAdmin(PlanetStackBaseAdmin):
+    model = SiteRole
+    pass
+
 class RoleAdmin(PlanetStackBaseAdmin):
     fieldsets = [
-        ('Role', {'fields': ['role_type']})
+        ('Role', {'fields': ['role_type', 'description','content_type'],
+                  'classes':['collapse']})
     ]
-    list_display = ('role_type',)
+    inlines = [ MemberInline,]
+    list_display = ('role_type','description','content_type')
 
 
 class DeploymentAdminForm(forms.ModelForm):
@@ -155,47 +232,30 @@ class DeploymentAdminForm(forms.ModelForm):
     class Meta:
         model = Deployment
 
-    def __init__(self, *args, **kwargs):
-        super(DeploymentAdminForm, self).__init__(*args, **kwargs)
-
-        if self.instance and self.instance.pk:
-            self.fields['sites'].initial = self.instance.sites.all()
-
-    def save(self, commit=True):
-        deploymentNetwork = super(DeploymentAdminForm, self).save(commit=False)
-        if commit:
-            deploymentNetwork.save()
-
-        if deploymentNetwork.pk:
-            deploymentNetwork.sites = self.cleaned_data['sites']
-            self.save_m2m()
-
-        return deploymentNetwork
 
 class DeploymentAdmin(PlanetStackBaseAdmin):
     form = DeploymentAdminForm
-    inlines = [NodeInline,SliverInline]
-
-    def get_formsets(self, request, obj=None):
-        for inline in self.get_inline_instances(request, obj):
-            # hide MyInline in the add view
-            if obj is None:
-                continue
-            # give inline object access to driver and caller
-            auth = request.session.get('auth', {})
-            if request.user.site:
-                auth['tenant'] = request.user.site.login_base
-            inline.model.os_manager = OpenStackManager(auth=auth, caller=request.user)
-            yield inline.get_formset(request, obj)
+    inlines = [MemberInline,NodeInline,SliverInline,TagInline]
+    fieldsets = [
+        (None, {'fields': ['sites'], 'classes':['suit-tab suit-tab-sites']}),]
+    suit_form_tabs =(('sites', 'Sites'),('nodes','Nodes'),('members','Members'),('tags','Tags'))
 
 class SiteAdmin(PlanetStackBaseAdmin):
     fieldsets = [
-        (None, {'fields': ['name', 'site_url', 'enabled', 'is_public', 'login_base', 'location']}),
-        ('Deployment Networks', {'fields': ['deployments']})
+        (None, {'fields': ['name', 'site_url', 'enabled', 'is_public', 'login_base', 'location'], 'classes':['suit-tab suit-tab-general']}),
+        ('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
     ]
+    suit_form_tabs =(('general', 'Site Details'),
+        ('users','Users'),
+        ('members','Privileges'),
+        ('deployments','Deployments'),
+        ('slices','Slices'),
+        ('nodes','Nodes'), 
+        ('tags','Tags'),
+    )
     list_display = ('name', 'login_base','site_url', 'enabled')
     filter_horizontal = ('deployments',)
-    inlines = [TagInline, NodeInline, UserInline, SitePrivilegeInline]
+    inlines = [SliceInline,UserInline,TagInline, NodeInline, MemberInline]
     search_fields = ['name']
 
     def queryset(self, request):
@@ -229,7 +289,7 @@ class SiteAdmin(PlanetStackBaseAdmin):
 
 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
     fieldsets = [
-        (None, {'fields': ['user', 'site', 'role']})
+        (None, {'fields': ['user', 'site', 'role'], 'classes':['collapse']})
     ]
     list_display = ('user', 'site', 'role')
 
@@ -269,9 +329,17 @@ class SitePrivilegeAdmin(PlanetStackBaseAdmin):
         return qs
 
 class SliceAdmin(PlanetStackBaseAdmin):
-    fields = ['name', 'site', 'serviceClass', 'description', 'slice_url']
+    fieldsets = [('Slice Details', {'fields': ['name', 'site', 'serviceClass', 'description', 'slice_url'], 'classes':['suit-tab suit-tab-general']}),]
     list_display = ('name', 'site','serviceClass', 'slice_url')
-    inlines = [SliverInline, SliceMembershipInline, TagInline, SliceTagInline]
+    inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline]
+
+
+    suit_form_tabs =(('general', 'Slice Details'),
+        ('sliceprivileges','Privileges'),
+        ('slivers','Slivers'),
+        ('tags','Tags'),
+        ('reservations','Reservations'),
+    )
 
     def formfield_for_foreignkey(self, db_field, request, **kwargs):
         if db_field.name == 'site':
@@ -317,7 +385,7 @@ class SliceAdmin(PlanetStackBaseAdmin):
         obj.caller = request.user
         obj.save() 
 
-class SliceMembershipAdmin(PlanetStackBaseAdmin):
+class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
     fieldsets = [
         (None, {'fields': ['user', 'slice', 'role']})
     ]
@@ -344,12 +412,12 @@ class SliceMembershipAdmin(PlanetStackBaseAdmin):
                 users = User.objects.filter(email__in=emails)
                 kwargs['queryset'] = users
 
-        return super(SliceMembershipAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
+        return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
 
     def queryset(self, request):
         # admins can see all memberships. Users can only see memberships of
         # slices where they have the admin role.
-        qs = super(SliceMembershipAdmin, self).queryset(request)
+        qs = super(SlicePrivilegeAdmin, self).queryset(request)
         if not request.user.is_admin:
             roles = Role.objects.filter(role_type__in=['admin', 'pi'])
             site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
@@ -374,13 +442,33 @@ class SliceMembershipAdmin(PlanetStackBaseAdmin):
         obj.delete()
 
 
-class ImageAdmin(admin.ModelAdmin):
-    fields = ['image_id', 'name', 'disk_format', 'container_format']
+class ImageAdmin(PlanetStackBaseAdmin):
+
+    fieldsets = [('Image Details', 
+                   {'fields': ['image_id', 'name', 'disk_format', 'container_format'], 
+                    'classes': ['suit-tab suit-tab-general']})
+               ]
+
+    suit_form_tabs =(('general','Image Details'),('slivers','Slivers'))
+
+    inlines = [SliverInline]
+
+class NodeForm(forms.ModelForm):
+    class Meta:
+        widgets = {
+            'site': LinkedSelect,
+            'deployment': LinkedSelect
+        }
 
 class NodeAdmin(admin.ModelAdmin):
+    form = NodeForm
+    exclude = ['enacted']
     list_display = ('name', 'site', 'deployment')
     list_filter = ('deployment',)
-    inlines = [TagInline]
+    inlines = [TagInline,SliverInline]
+    fieldsets = [('Node Details', {'fields': ['name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
+
+    suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
 
 
 class SliverForm(forms.ModelForm):
@@ -391,26 +479,41 @@ class SliverForm(forms.ModelForm):
         widgets = {
             'ip': PlainTextWidget(),
             'instance_name': PlainTextWidget(),
+            'slice': LinkedSelect,
+            'deploymentNetwork': LinkedSelect,
+            'node': LinkedSelect,
+            'image': LinkedSelect
         }
 
 class ProjectAdmin(admin.ModelAdmin):
     exclude = ['enacted']
+    inlines = [TagInline]
+
+class MemberAdmin(admin.ModelAdmin):
+    exclude = ['enacted']
+    list_display = ['role', 'rightContent_type', 'content_type', 'content_object',]
 
 class TagAdmin(admin.ModelAdmin):
     exclude = ['enacted']
+    list_display = ['project', 'name', 'value', 'content_type', 'content_object',]
 
 class SliverAdmin(PlanetStackBaseAdmin):
     form = SliverForm
     fieldsets = [
-        ('Sliver', {'fields': ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']})
+        ('Sliver Details', {'fields': ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
     ]
     list_display = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']
+
+    suit_form_tabs =(('general', 'Sliver Details'),
+        ('tags','Tags'),
+    )
+
     inlines = [TagInline]
 
     def formfield_for_foreignkey(self, db_field, request, **kwargs):
         if db_field.name == 'slice':
             if not request.user.is_admin:
-                slices = set([sm.slice.name for sm in SliceMembership.objects.filter(user=request.user)]) 
+                slices = set([sm.slice.name for sm in SlicePrivilege.objects.filter(user=request.user)]) 
                 kwargs['queryset'] = Slice.objects.filter(name__in=list(slices))
 
         return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
@@ -470,7 +573,7 @@ class UserCreationForm(forms.ModelForm):
 
     class Meta:
         model = User
-        fields = ('email', 'firstname', 'lastname', 'phone', 'public_key', 'site')
+        fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
 
     def clean_password2(self):
         # Check that the two password entries match
@@ -518,24 +621,26 @@ class UserAdmin(UserAdmin):
     # The fields to be used in displaying the User model.
     # These override the definitions on the base UserAdmin
     # that reference specific fields on auth.User.
-    list_display = ('email', 'site', 'firstname', 'lastname', 'is_admin', 'last_login')
-    list_filter = ('site',)
-    inlines = [SitePrivilegeInline, SliceMembershipInline]
+    list_display = ('email', 'username','firstname', 'lastname', 'is_admin', 'last_login')
+    list_filter = ()
+    inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline]
     fieldsets = (
-        (None, {'fields': ('email', 'password', 'site', 'is_admin', 'timezone')}),
-        ('Personal info', {'fields': ('firstname','lastname','phone', 'public_key')}),
+        ('Login Details', {'fields': ('email', 'username','site','password', 'is_admin', 'public_key'), 'classes':['suit-tab suit-tab-general']}),
+        ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
         #('Important dates', {'fields': ('last_login',)}),
     )
     add_fieldsets = (
         (None, {
             'classes': ('wide',),
-            'fields': ('email', 'firstname', 'lastname', 'phone', 'site', 'public_key','password1', 'password2', 'is_admin')}
+            'fields': ('email', 'username','firstname', 'lastname', 'phone', 'public_key','password1', 'password2')}
         ),
     )
     search_fields = ('email',)
     ordering = ('email',)
     filter_horizontal = ()
 
+    suit_form_tabs =(('general','Login Details'),('contact','Contact Information'),('sliceprivileges','Slice Privileges'),('siteprivileges','Site Privileges'),('deploymentprivileges','Deployment Privileges'))
+
     def formfield_for_foreignkey(self, db_field, request, **kwargs):
         if db_field.name == 'site':
             if not request.user.is_admin:
@@ -562,6 +667,7 @@ class ReservedResourceInline(admin.TabularInline):
     exclude = ['enacted']
     model = ReservedResource
     extra = 0
+    suit_classes = 'suit-tab suit-tab-reservedresources'
 
     def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
         field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
@@ -586,6 +692,9 @@ class ReservedResourceInline(admin.TabularInline):
 class ReservationChangeForm(forms.ModelForm):
     class Meta:
         model = Reservation
+        widgets = {
+            'slice' : LinkedSelect
+        }
 
 class ReservationAddForm(forms.ModelForm):
     slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
@@ -603,6 +712,10 @@ class ReservationAddForm(forms.ModelForm):
 
     class Meta:
         model = Reservation
+        widgets = {
+            'slice' : LinkedSelect
+        }
+
 
 class ReservationAddRefreshForm(ReservationAddForm):
     """ This form is displayed when the Reservation Form receives an update
@@ -629,10 +742,14 @@ class ReservationAddRefreshForm(ReservationAddForm):
 
 class ReservationAdmin(admin.ModelAdmin):
     exclude = ['enacted']
+    fieldsets = [('Reservation Details', {'fields': ['startTime', 'duration','slice'], 'classes': ['suit-tab suit-tab-general']})]
     list_display = ('startTime', 'duration')
-    inlines = [ReservedResourceInline]
     form = ReservationAddForm
 
+    suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
+
+    inlines = [ReservedResourceInline]
+
     def add_view(self, request, form_url='', extra_context=None):
         timezone.activate(request.user.timezone)
         request._refresh = False
@@ -700,7 +817,7 @@ admin.site.unregister(Evolution)
 
 # When debugging it is often easier to see all the classes, but for regular use 
 # only the top-levels should be displayed
-showAll = False
+showAll = True
 
 admin.site.register(Deployment, DeploymentAdmin)
 admin.site.register(Site, SiteAdmin)
@@ -708,13 +825,19 @@ admin.site.register(Slice, SliceAdmin)
 admin.site.register(Project, ProjectAdmin)
 admin.site.register(ServiceClass, ServiceClassAdmin)
 admin.site.register(Reservation, ReservationAdmin)
+#admin.site.register(SliceRole, SliceRoleAdmin)
+#admin.site.register(SiteRole, SiteRoleAdmin)
+#admin.site.register(PlanetStackRole)
+#admin.site.register(DeploymentRole)
 
 if showAll:
+    #admin.site.register(PlanetStack)
     admin.site.register(Tag, TagAdmin)
     admin.site.register(Node, NodeAdmin)
-    admin.site.register(SliceMembership, SliceMembershipAdmin)
-    admin.site.register(SitePrivilege, SitePrivilegeAdmin)
+    #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
+    #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
     admin.site.register(Role, RoleAdmin)
+    admin.site.register(Member, MemberAdmin)
     admin.site.register(Sliver, SliverAdmin)
     admin.site.register(Image, ImageAdmin)
 
index a86728a..f034820 100644 (file)
         "maxDuration": 8760, 
         "enacted": null
     }
+},
+{
+    "pk": 1, 
+    "model": "core.role", 
+    "fields": {
+        "updated": "2013-07-30T10:35:50.572Z", 
+        "description": "PlanetStack Application Administrator", 
+        "created": "2013-07-30T10:30:28.633Z", 
+        "content_type": 8, 
+        "role_type": "Admin", 
+        "enacted": null
+    }
+},
+{
+    "pk": 2, 
+    "model": "core.role", 
+    "fields": {
+        "updated": "2013-07-30T10:36:30.174Z", 
+        "description": "User level role for PlanetStack Application", 
+        "created": "2013-07-30T10:31:02.627Z", 
+        "content_type": 8, 
+        "role_type": "Default", 
+        "enacted": null
+    }
+},
+{
+    "pk": 3, 
+    "model": "core.role", 
+    "fields": {
+        "updated": "2013-07-30T10:36:07.877Z", 
+        "description": "Administrative role for a Slice", 
+        "created": "2013-07-30T10:31:25.829Z", 
+        "content_type": 21, 
+        "role_type": "Admin", 
+        "enacted": null
+    }
+},
+{
+    "pk": 4, 
+    "model": "core.role", 
+    "fields": {
+        "updated": "2013-07-30T10:36:20.679Z", 
+        "description": "User level access for a particular Slice", 
+        "created": "2013-07-30T10:31:48.791Z", 
+        "content_type": 21, 
+        "role_type": "Default", 
+        "enacted": null
+    }
+},
+{
+    "pk": 5, 
+    "model": "core.role", 
+    "fields": {
+        "updated": "2013-07-30T10:35:59.388Z", 
+        "description": "Administrator role for a particular Site", 
+        "created": "2013-07-30T10:32:20.600Z", 
+        "content_type": 15, 
+        "role_type": "Admin", 
+        "enacted": null
+    }
+},
+{
+    "pk": 6, 
+    "model": "core.role", 
+    "fields": {
+        "updated": "2013-07-30T10:34:39.494Z", 
+        "description": "Principal Investigator for a particular Site", 
+        "created": "2013-07-30T10:34:39.494Z", 
+        "content_type": 15, 
+        "role_type": "PI", 
+        "enacted": null
+    }
+},
+{
+    "pk": 7, 
+    "model": "core.role", 
+    "fields": {
+        "updated": "2013-07-30T10:35:27.633Z", 
+        "description": "Technical support for a particular Site. Allows for Read/Write access to a Site's Nodes.", 
+        "created": "2013-07-30T10:35:27.633Z", 
+        "content_type": 15, 
+        "role_type": "Tech", 
+        "enacted": null
+    }
+},
+{
+    "pk": 8, 
+    "model": "core.role", 
+    "fields": {
+        "updated": "2013-07-30T10:38:04.554Z", 
+        "description": "Responsibility for a particular Site's accounting and invoices.", 
+        "created": "2013-07-30T10:38:04.554Z", 
+        "content_type": 15, 
+        "role_type": "Billing", 
+        "enacted": null
+    }
+},
+{
+    "pk": 9, 
+    "model": "core.role", 
+    "fields": {
+        "updated": "2013-07-30T10:39:07.062Z", 
+        "description": "Default access for a particular Site which allows for Read-only access to the Site's objects.", 
+        "created": "2013-07-30T10:39:07.062Z", 
+        "content_type": 15, 
+        "role_type": "Default", 
+        "enacted": null
+    }
+},
+{
+    "pk": 10, 
+    "model": "core.role", 
+    "fields": {
+        "updated": "2013-07-30T10:39:55.479Z", 
+        "description": "Represents the Site through which a particular user is managed through.", 
+        "created": "2013-07-30T10:39:55.479Z", 
+        "content_type": 15, 
+        "role_type": "Homed", 
+        "enacted": null
+    }
+},
+{
+    "pk": 11,
+    "model": "core.role",
+    "fields": {
+        "updated": "2013-07-30T17:35:59.815Z",
+        "description": "Administrative responsibility for a particular Deployment.",
+        "created": "2013-07-30T17:35:59.815Z",
+        "content_type": 9,
+        "role_type": "Admin",
+        "enacted": null
+    }
+},
+{
+    "pk": 12,
+    "model": "core.role",
+    "fields": {
+        "updated": "2013-07-30T17:36:54.728Z",
+        "description": "Default access for a particular Deployment.",
+        "created": "2013-07-30T17:36:54.728Z",
+        "content_type": 9,
+        "role_type": "Default",
+        "enacted": null
+    }
+},
+{
+    "pk": 1,
+    "model": "core.planetstackrole",
+    "fields": {
+        "updated": "2013-09-03T11:47:42.611Z",
+        "enacted": "2013-09-03T11:47:51Z",
+        "role": "admin",
+        "created": "2013-09-03T11:47:42.611Z"
+    }
+},
+{
+    "pk": 1,
+    "model": "core.siterole",
+    "fields": {
+        "updated": "2013-09-03T11:48:34.966Z",
+        "enacted": null,
+        "role": "admin",
+        "created": "2013-09-03T11:48:34.966Z"
+    }
+},
+{
+    "pk": 2,
+    "model": "core.siterole",
+    "fields": {
+        "updated": "2013-09-03T11:48:49.480Z",
+        "enacted": null,
+        "role": "pi",
+        "created": "2013-09-03T11:48:49.480Z"
+    }
+},
+{
+    "pk": 3,
+    "model": "core.siterole",
+    "fields": {
+        "updated": "2013-09-03T11:49:03.678Z",
+        "enacted": null,
+        "role": "tech",
+        "created": "2013-09-03T11:49:03.678Z"
+    }
+},
+{
+    "pk": 4,
+    "model": "core.siterole",
+    "fields": {
+        "updated": "2013-09-03T11:49:17.254Z",
+        "enacted": null,
+        "role": "billing",
+        "created": "2013-09-03T11:49:17.254Z"
+    }
+},
+{
+    "pk": 1,
+    "model": "core.slicerole",
+    "fields": {
+        "updated": "2013-09-03T11:48:02.080Z",
+        "enacted": null,
+        "role": "admin",
+        "created": "2013-09-03T11:48:02.080Z"
+    }
+},
+{
+    "pk": 2,
+    "model": "core.slicerole",
+    "fields": {
+        "updated": "2013-09-03T11:48:17.688Z",
+        "enacted": null,
+        "role": "default",
+        "created": "2013-09-03T11:48:17.688Z"
+    }
+},
+{
+    "pk": 1,
+    "model": "core.deploymentrole",
+    "fields": {
+        "updated": "2013-09-03T11:48:02.080Z",
+        "enacted": null,
+        "role": "admin",
+        "created": "2013-09-03T11:48:02.080Z"
+    }
 }
 ]
index 2280822..1cc4d07 100644 (file)
@@ -1,17 +1,25 @@
 from .plcorebase import PlCoreBase
-from .deployment import Deployment
+from .planetstack import PlanetStack
 from .project import Project
 from .tag import Tag
+from .role import Role
+from .deployment import Deployment
 from .site import Site
+from .user import User
+from .serviceclass import ServiceClass
+from .slice import Slice
 from .site import SitePrivilege
 from .image import Image
-from .user import User
-from .role import Role
 from .node import Node
-from .serviceclass import ServiceClass
 from .serviceresource import ServiceResource
-from .slice import Slice
-from .slice import SliceMembership
+from .slice import SliceRole
+from .slice import SlicePrivilege
+from .site import SiteRole
+from .site import SitePrivilege
+from .deployment import DeploymentRole
+from .deployment import DeploymentPrivilege
+from .planetstack import PlanetStackRole
+from .planetstack import PlanetStackPrivilege
 from .slicetag import SliceTag
 from .sliver import Sliver
 from .reservation import ReservedResource
index d38115f..4e835d0 100644 (file)
@@ -1,11 +1,30 @@
 import os
 from django.db import models
 from core.models import PlCoreBase
+from core.models import Member
+from django.contrib.contenttypes import generic
 
 # Create your models here.
 
 class Deployment(PlCoreBase):
     name = models.CharField(max_length=200, unique=True, help_text="Name of the Deployment")
+    members = generic.GenericRelation(Member)
 
     def __unicode__(self):  return u'%s' % (self.name)
 
+    
+class DeploymentRole(PlCoreBase):
+
+    ROLE_CHOICES = (('admin','Admin'),)
+    role = models.CharField(choices=ROLE_CHOICES, unique=True, max_length=30)
+
+    def __unicode__(self):  return u'%s' % (self.role)
+
+class DeploymentPrivilege(PlCoreBase):
+
+    user = models.ForeignKey('User', related_name='deployment_privileges')
+    deployment = models.ForeignKey('Deployment', related_name='deployment_privileges')
+    role = models.ForeignKey('DeploymentRole')
+
+    def __unicode__(self):  return u'%s %s %s' % (self.deployment, self.user, self.role)
+
diff --git a/planetstack/core/models/planetstack.py b/planetstack/core/models/planetstack.py
new file mode 100644 (file)
index 0000000..9007a51
--- /dev/null
@@ -0,0 +1,30 @@
+import os
+from django.db import models
+from core.models import PlCoreBase
+
+# Create your models here.
+
+class PlanetStack(PlCoreBase):
+    description = models.CharField(max_length=200, unique=True, default="PlanetStack", help_text="Used for scoping of roles at the PlanetStack Application level")
+
+    class Meta:
+        verbose_name_plural = "PlanetStack"
+        app_label = "core"
+
+    def __unicode__(self):  return u'%s' % (self.description)
+
+class PlanetStackRole(PlCoreBase):
+    ROLE_CHOICES = (('admin','Admin'),)
+    role = models.CharField(choices=ROLE_CHOICES, unique=True, max_length=30)
+
+    def __unicode__(self):  return u'%s' % (self.role)
+
+class PlanetStackPrivilege(PlCoreBase):
+    user = models.ForeignKey('User', related_name='planetstack_privileges')
+    planetstack = models.ForeignKey('PlanetStack', related_name='planetstack_privileges', default=1)
+    role = models.ForeignKey('PlanetStackRole')
+
+    def __unicode__(self):  return u'%s %s %s' % (self.planetstack, self.user, self.role)
+
+
+
index fd29848..234868e 100644 (file)
@@ -2,14 +2,16 @@ import os
 import datetime
 from django.db import models
 from core.models import PlCoreBase
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.contenttypes import generic
 
 class Role(PlCoreBase):
 
-    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)
+    role_type = models.CharField(max_length=80, verbose_name="Name")
+    description = models.CharField(max_length=120, verbose_name="Description")
+    content_type = models.ForeignKey(ContentType, verbose_name="Role Scope")
 
-    def __unicode__(self):  return u'%s' % (self.role_type)
+    def __unicode__(self):  return u'%s:%s' % (self.content_type,self.role_type)
 
 
     def save(self, *args, **kwds):
index 8a6d7c4..aee3843 100644 (file)
@@ -24,11 +24,18 @@ class Site(PlCoreBase):
 
     def __unicode__(self):  return u'%s' % (self.name)
 
+class SiteRole(PlCoreBase):
+
+    ROLE_CHOICES = (('admin','Admin'),('pi','PI'),('tech','Tech'),('billing','Billing'))
+    role = models.CharField(choices=ROLE_CHOICES, unique=True, max_length=30)
+
+    def __unicode__(self):  return u'%s' % (self.role)
+
 class SitePrivilege(PlCoreBase):
 
     user = models.ForeignKey('User', related_name='site_privileges')
     site = models.ForeignKey('Site', related_name='site_privileges')
-    role = models.ForeignKey('Role')
+    role = models.ForeignKey('SiteRole')
 
     def __unicode__(self):  return u'%s %s %s' % (self.site, self.user, self.role)
 
index 74815b2..e584c07 100644 (file)
@@ -40,15 +40,16 @@ class Slice(PlCoreBase):
             self.creator = self.caller
         super(Slice, self).save(*args, **kwds)
 
-class SliceMembership(PlCoreBase):
-    user = models.ForeignKey('User', related_name='slice_memberships')
-    slice = models.ForeignKey('Slice', related_name='slice_memberships')
-    role = models.ForeignKey('Role')
+class SliceRole(PlCoreBase):
+    ROLE_CHOICES = (('admin','Admin'),('default','Default'))
 
-    def __unicode__(self):  return u'%s %s %s' % (self.slice, self.user, self.role)
+    role = models.CharField(choices=ROLE_CHOICES, unique=True, max_length=30)
 
-    def save(self, *args, **kwds):
-        super(SliceMembership, self).save(*args, **kwds)
+    def __unicode__(self):  return u'%s' % (self.role)
 
-    def delete(self, *args, **kwds):
-        super(SliceMembership, self).delete(*args, **kwds)
+class SlicePrivilege(PlCoreBase):
+    user = models.ForeignKey('User', related_name='slice_privileges')
+    slice = models.ForeignKey('Slice', related_name='slice_privileges')
+    role = models.ForeignKey('SliceRole')
+
+    def __unicode__(self):  return u'%s %s %s' % (self.slice, self.user, self.role)
index 758bcbf..2b63dda 100644 (file)
@@ -2,11 +2,11 @@ import os
 import datetime
 from collections import defaultdict
 from django.db import models
-from core.models import PlCoreBase
-from core.models import Site
+from core.models import PlCoreBase,Site
 from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
 from timezones.fields import TimeZoneField
 
+
 # Create your models here.
 class UserManager(BaseUserManager):
     def create_user(self, email, firstname, lastname, password=None):
@@ -54,6 +54,7 @@ class User(AbstractBaseUser):
         unique=True,
         db_index=True,
     )
+    username = models.CharField(max_length=200, default="Something" )
 
     kuser_id = models.CharField(null=True, blank=True, help_text="keystone user id", max_length=200) 
     firstname = models.CharField(help_text="person's given name", max_length=200)
@@ -61,7 +62,7 @@ class User(AbstractBaseUser):
 
     phone = models.CharField(null=True, blank=True, help_text="phone number contact", max_length=100)
     user_url = models.URLField(null=True, blank=True)
-    site = models.ForeignKey(Site, related_name='users', verbose_name="Site this user will be homed too", null=True)
+    site = models.ForeignKey(Site, related_name='users', help_text="Site this user will be homed too", null=True)
     public_key = models.TextField(null=True, blank=True, max_length=1024, help_text="Public key string")
 
     is_active = models.BooleanField(default=True)
@@ -104,18 +105,21 @@ class User(AbstractBaseUser):
         # Simplest possible answer: Yes, always
         return True
 
-    def get_roles(self):
-        from core.models.site import SitePrivilege
-        from core.models.slice import SliceMembership
-
-        site_privileges = SitePrivilege.objects.filter(user=self)
-        slice_memberships = SliceMembership.objects.filter(user=self)
-        roles = defaultdict(list)
-        for site_privilege in site_privileges:
-            roles[site_privilege.role.role_type].append(site_privilege.site.login_base)
-        for slice_membership in slice_memberships:
-            roles[slice_membership.role.role_type].append(slice_membership.slice.name)
-        return roles   
+    def is_superuser(self):
+        return False
+
+#    def get_roles(self):
+#        from core.models.site import SitePrivilege
+#        from core.models.slice import SliceMembership
+#
+#        site_privileges = SitePrivilege.objects.filter(user=self)
+#        slice_memberships = SliceMembership.objects.filter(user=self)
+#        roles = defaultdict(list)
+#        for site_privilege in site_privileges:
+#            roles[site_privilege.role.role_type].append(site_privilege.site.login_base)
+#        for slice_membership in slice_memberships:
+#            roles[slice_membership.role.role_type].append(slice_membership.slice.name)
+#        return roles   
 
     def save(self, *args, **kwds):
         if not self.id:
index 94f5c3c..b83157b 100644 (file)
@@ -3,38 +3,108 @@ from rest_framework import serializers
 from core.models import *
 
 
+class DeploymentSerializer(serializers.HyperlinkedModelSerializer):
+
+    # HyperlinkedModelSerializer doesn't include the id by default
+    id = serializers.Field()
+    sites = serializers.HyperlinkedRelatedField(view_name='site-detail')
+    class Meta:
+        model = Deployment
+        fields = ('id',
+                  'url',
+                  'name',
+                  'sites'
+                 )
+
+class ImageSerializer(serializers.HyperlinkedModelSerializer):
+    # HyperlinkedModelSerializer doesn't include the id by default
+    id = serializers.Field()
+    class Meta:
+        model = Image
+        fields = ('id',
+                  'url',
+                  'image_id',
+                  'name',
+                  'disk_format',
+                  'container_format')
+
+class NodeSerializer(serializers.HyperlinkedModelSerializer):
+    # HyperlinkedModelSerializer doesn't include the id by default
+    id = serializers.Field()
+    class Meta:
+        model = Node
+        fields = ('id',
+                 'url',
+                 'name')
+
+class ProjectSerializer(serializers.HyperlinkedModelSerializer):
+    # HyperlinkedModelSerializer doesn't include the id by default
+    id = serializers.Field()
+    class Meta:
+        model = Project
+        fields = ('id',
+                 'url',
+                 'name')
+
+class ReservationSerializer(serializers.HyperlinkedModelSerializer):
+    # HyperlinkedModelSerializer doesn't include the id by default
+    id = serializers.Field()
+    class Meta:
+        model = Reservation
+        fields = ('id',
+                 'url',
+                 'startTime',
+                 'slice',
+                 'duration',
+                 'endTime',
+                 )
+
 class RoleSerializer(serializers.HyperlinkedModelSerializer):
     # HyperlinkedModelSerializer doesn't include the id by default
     id = serializers.Field()
     class Meta:
         model = Role
         fields = ('id', 
-                  'role',
-                  'role_type')
+                 'url',
+                 'role',
+                 'role_type')
 
 
-class UserSerializer(serializers.HyperlinkedModelSerializer):
+class ServiceClassSerializer(serializers.HyperlinkedModelSerializer):
     # HyperlinkedModelSerializer doesn't include the id by default
     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='siteprivilege-detail')
     class Meta:
-        model = User
+        model = ServiceClass
         fields = ('id',
-                  'kuser_id', 
-                  'firstname', 
-                  'lastname',
-                  'email', 
-                  'password',
-                  'phone',
-                  'public_key', 
-                  'user_url',
-                  'is_admin',
-                  'site',
-                  'slice_memberships',
-                  'site_privileges')
-                    
+                 'url',
+                 'name',
+                 'description',
+                 'commitment',
+                 'membershipFee',
+                 'membershipFeeMonths',
+                 'upgradeRequiresApproval',
+                 'upgradeFrom',
+                 )
+
+class ServiceResourceSerializer(serializers.HyperlinkedModelSerializer):
+    # HyperlinkedModelSerializer doesn't include the id by default
+    id = serializers.Field()
+    serviceClass = serializers.HyperlinkedRelatedField(view_name='serviceclass-detail')
+    class Meta:
+        model = ServiceResource
+        fields = ('id',
+                 'url',
+                 'name',
+                 'serviceClass',
+                 'maxUnitsDeployment',
+                 'maxUnitsNode',
+                 'maxDuration',
+                 'bucketInRate',
+                 'bucketMaxSize',
+                 'cost',
+                 'calendarReservable',
+                 )
+
 class SliceSerializer(serializers.HyperlinkedModelSerializer):
     # HyperlinkedModelSerializer doesn't include the id by default
     id = serializers.Field()
@@ -43,6 +113,7 @@ class SliceSerializer(serializers.HyperlinkedModelSerializer):
     class Meta:
         model = Slice
         fields = ('id',
+                  'url',
                   'tenant_id',
                   'enabled',
                   'name',
@@ -58,14 +129,15 @@ class SliceSerializer(serializers.HyperlinkedModelSerializer):
                   'updated',
                   'created')
 
-class SliceMembershipSerializer(serializers.HyperlinkedModelSerializer):
+class SlicePrivilegeSerializer(serializers.HyperlinkedModelSerializer):
     id = serializers.Field()
     slice = serializers.HyperlinkedRelatedField(view_name='slice-detail')
     user = serializers.HyperlinkedRelatedField(view_name='user-detail')
     role = serializers.HyperlinkedRelatedField(view_name='role-detail')
     class Meta:
-        model = SliceMembership
+        model = SlicePrivilege
         fields = ('id',
+                  'url',
                   'user',
                   'slice',
                   'role')
@@ -105,75 +177,90 @@ class SitePrivilegeSerializer(serializers.HyperlinkedModelSerializer):
     class Meta:
         model = SitePrivilege
         fields = ('id',
+                  'url',
                   'user',
                   'site',
                   'role')
 
-class DeploymentSerializer(serializers.HyperlinkedModelSerializer):
-
-    # HyperlinkedModelSerializer doesn't include the id by default
-    id = serializers.Field()
-    sites = serializers.HyperlinkedRelatedField(view_name='site-detail')
-    class Meta:
-        model = Deployment
-        fields = ('id',
-                  'name',
-                  'sites'
-                 )
-
 class SliverSerializer(serializers.HyperlinkedModelSerializer):
     # HyperlinkedModelSerializer doesn't include the id by default
     id = serializers.Field()
     image = serializers.HyperlinkedRelatedField(view_name='image-detail')
     slice = serializers.HyperlinkedRelatedField(view_name='slice-detail')
-    deployment = serializers.HyperlinkedRelatedField(view_name='deployment-detail')
+    deploymentNetwork = serializers.HyperlinkedRelatedField(view_name='deployment-detail')
     node = serializers.HyperlinkedRelatedField(view_name='node-detail')
     
-    
     #slice = serializers.PrimaryKeyRelatedField(read_only=True)
 
     class Meta:
         model = Sliver
         fields = ('id',
+                  'url',
                   'instance_id',
                   'name',
                   'instance_name',
                   'ip',
                   'image',
                   'slice',
-                  'deployment',
+                  'deploymentNetwork',
                   'node')
 
-class NodeSerializer(serializers.HyperlinkedModelSerializer):
+class UserSerializer(serializers.HyperlinkedModelSerializer):
     # HyperlinkedModelSerializer doesn't include the id by default
     id = serializers.Field()
+    site = serializers.HyperlinkedRelatedField(view_name='site-detail')
+    slice_privileges = serializers.HyperlinkedRelatedField(view_name='sliceprivilege-detail')
+    site_privileges = serializers.HyperlinkedRelatedField(view_name='siteprivilege-detail')
     class Meta:
-        model = Node
+        model = User
         fields = ('id',
-                 'name')
-
-class ImageSerializer(serializers.HyperlinkedModelSerializer):
+                  'url',
+                  'kuser_id', 
+                  'firstname', 
+                  'lastname',
+                  'email', 
+                  'password',
+                  'phone',
+                  'public_key', 
+                  'user_url',
+                  'is_admin',
+                  'slice_privileges',
+                  'site_privileges')
+                    
+class TagSerializer(serializers.HyperlinkedModelSerializer):
     # HyperlinkedModelSerializer doesn't include the id by default
     id = serializers.Field()
+    project = serializers.HyperlinkedRelatedField(view_name='project-detail')
+    #content_type = serializers.PrimaryKeyRelatedField(read_only=True)
+    content_type = serializers.RelatedField(source = "content_type")
+    content_object = serializers.RelatedField(source='content_object')
     class Meta:
-        model = Image
-        fields = ('id',
-                  'image_id',
-                  'name',
-                  'disk_format',
-                  'container_format')
+        model = Tag
+        fields = ('id', 
+                  'url',
+                  'project',
+                  'value',
+                  'content_type',
+                  'object_id',
+                  'content_object',
+                  'name')
 
 serializerLookUp = { 
+                 Deployment: DeploymentSerializer,
+                 Image: ImageSerializer,
+                 Node: NodeSerializer,
+                 Project: ProjectSerializer,
+                 Reservation: ReservationSerializer,
                  Role: RoleSerializer,
-                 User: UserSerializer,
+                 ServiceClass: ServiceClassSerializer,
+                 ServiceResource: ServiceResourceSerializer,
                  Site: SiteSerializer,
                  SitePrivilege: SitePrivilegeSerializer,
                  Slice: SliceSerializer,
-                 SliceMembership: SliceMembershipSerializer,
-                 Node: NodeSerializer,
+                 SlicePrivilege: SlicePrivilegeSerializer,
                  Sliver: SliverSerializer,
-                 Deployment: DeploymentSerializer,
-                 Image: ImageSerializer,
+                 Tag: TagSerializer,
+                 User: UserSerializer,
                  None: None,
                 }
 
index 2034708..b517eac 100644 (file)
@@ -1 +1,2 @@
-.field-refresh { display: none; }
+.required:after {color: red ! important; font-size: 18px }
+#.btn-success {color:black}
diff --git a/planetstack/core/views/slice_memberships.py b/planetstack/core/views/slice_memberships.py
deleted file mode 100644 (file)
index 13f0707..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-from core.serializers import SliceMembershipSerializer
-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
-
-
diff --git a/planetstack/core/views/slice_privileges.py b/planetstack/core/views/slice_privileges.py
new file mode 100644 (file)
index 0000000..4dd1f93
--- /dev/null
@@ -0,0 +1,13 @@
+from core.serializers import SlicePrivilegeSerializer
+from rest_framework import generics
+from core.models import SlicePrivilege
+
+class SlicePrivilegeList(generics.ListCreateAPIView):
+    queryset = SlicePrivilege.objects.all()
+    serializer_class = SlicePrivilegeSerializer
+
+class SlicePrivilegeDetail(generics.RetrieveUpdateDestroyAPIView):
+    queryset = SlicePrivilege.objects.all()
+    serializer_class = SlicePrivilegeSerializer
+
+
diff --git a/planetstack/hpc/__init__.py b/planetstack/hpc/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/planetstack/hpc/admin.py b/planetstack/hpc/admin.py
new file mode 100644 (file)
index 0000000..3afb448
--- /dev/null
@@ -0,0 +1,68 @@
+from django.contrib import admin
+
+from hpc.models import *
+from django import forms
+from django.utils.safestring import mark_safe
+from django.contrib.auth.admin import UserAdmin
+from django.contrib.admin.widgets import FilteredSelectMultiple
+from django.contrib.auth.forms import ReadOnlyPasswordHashField
+from django.contrib.auth.signals import user_logged_in
+from django.utils import timezone
+from django.contrib.contenttypes import generic
+from suit.widgets import LinkedSelect
+
+#class HPCRRBaseAdmin(admin.ModelAdmin):
+    #exclude = ['enacted']
+
+class CDNPrefixInline(admin.TabularInline):
+    model = CDNPrefix
+    extra = 0
+    suit_classes = 'suit-tab suit-tab-prefixes'
+
+class ContentProviderInline(admin.TabularInline):
+    model = ContentProvider
+    extra = 0
+    suit_classes = 'suit-tab suit-tab-cps'
+
+class OriginServerAdmin(admin.ModelAdmin):
+    list_display = ('url','protocol','redirects','contentProvider','authenticated','enabled' )
+
+class ContentProviderForm(forms.ModelForm):
+    class Meta:
+        widgets = {
+            'serviceProvider' : LinkedSelect
+        }
+
+class ContentProviderAdmin(admin.ModelAdmin):
+    form = ContentProviderForm
+    list_display = ('name','description','enabled' )
+    fieldsets = [ (None, {'fields': ['name','enabled','description','serviceProvider','users'], 'classes':['suit-tab suit-tab-general']})]
+
+    inlines = [CDNPrefixInline]
+
+    suit_form_tabs = (('general','Details'),('prefixes','CDN Prefixes'))
+
+class ServiceProviderAdmin(admin.ModelAdmin):
+    list_display = ('name', 'description', 'enabled')
+    fieldsets = [
+        (None, {'fields': ['name','description','enabled'], 'classes':['suit-tab suit-tab-general']})]
+#, ('Content Providers', {'fields':['contentProviders'],'classes':['suit-tab suit-tab-cps']})] 
+
+    suit_form_tabs = (('general','Details'),('cps','Content Providers'))
+    inlines = [ContentProviderInline]
+
+class CDNPrefixForm(forms.ModelForm):
+    class Meta:
+        widgets = {
+            'contentProvider' : LinkedSelect
+        }
+
+class CDNPrefixAdmin(admin.ModelAdmin):
+    form = CDNPrefixForm
+    list_display = ['prefix','contentProvider']
+
+admin.site.register(ServiceProvider, ServiceProviderAdmin)
+admin.site.register(ContentProvider, ContentProviderAdmin)
+admin.site.register(CDNPrefix, CDNPrefixAdmin)
+admin.site.register(OriginServer,OriginServerAdmin)
+
diff --git a/planetstack/hpc/models.py b/planetstack/hpc/models.py
new file mode 100644 (file)
index 0000000..d257032
--- /dev/null
@@ -0,0 +1,92 @@
+from django.db import models
+from core.models import User
+import os
+from django.db import models
+from django.forms.models import model_to_dict
+
+
+# Create your models here.
+
+class HpcCoreBase(models.Model):
+
+    created = models.DateTimeField(auto_now_add=True)
+    updated = models.DateTimeField(auto_now=True)
+
+    class Meta:
+        abstract = True
+        app_label = "hpc"
+
+    def __init__(self, *args, **kwargs):
+        super(HpcCoreBase, self).__init__(*args, **kwargs)
+        self.__initial = self._dict
+
+    @property
+    def diff(self):
+        d1 = self.__initial
+        d2 = self._dict
+        diffs = [(k, (v, d2[k])) for k, v in d1.items() if v != d2[k]]
+        return dict(diffs)
+
+    @property
+    def has_changed(self):
+        return bool(self.diff)
+
+    @property
+    def changed_fields(self):
+        return self.diff.keys()
+
+    def get_field_diff(self, field_name):
+        return self.diff.get(field_name, None)
+
+    def save(self, *args, **kwargs):
+        super(HpcCoreBase, self).save(*args, **kwargs)
+
+        self.__initial = self._dict
+
+    @property
+    def _dict(self):
+        return model_to_dict(self, fields=[field.name for field in
+                             self._meta.fields])
+
+    
+class ServiceProvider(HpcCoreBase):
+    name = models.CharField(max_length=254,help_text="Service Provider Name")
+    description = models.TextField(max_length=254,null=True, blank=True, help_text="Description of Service Provider")
+    enabled = models.BooleanField(default=True)
+
+    def __unicode__(self):  return u'%s' % (self.name)
+
+class ContentProvider(HpcCoreBase):
+    name = models.CharField(max_length=254)
+    enabled = models.BooleanField(default=True)
+    description = models.TextField(max_length=254,null=True, blank=True,help_text="Description of Content Provider")
+    serviceProvider = models.ForeignKey(ServiceProvider)
+
+    # Note user relationships are directed not requiring a role.
+    users = models.ManyToManyField(User)
+
+    def __unicode__(self):  return u'%s' % (self.name)
+
+class OriginServer(HpcCoreBase):
+    url = models.URLField()
+    contentProvider = models.ForeignKey(ContentProvider)
+
+    authenticated = models.BooleanField(default=False, help_text="Status for this Site")
+    enabled = models.BooleanField(default=True, help_text="Status for this Site")
+    PROTOCOL_CHOICES = (('http', 'HTTP'),('rtmp', 'RTMP'), ('rtp', 'RTP'),('shout', 'SHOUTcast')) 
+    protocol = models.CharField(default="HTTP", max_length = 12, choices=PROTOCOL_CHOICES)
+    redirects = models.BooleanField(default=True, help_text="Indicates whether Origin Server redirects should be used for this Origin Server")
+    description = models.TextField(null=True, blank=True, max_length=255)
+    
+    def __unicode__(self):  return u'%s' % (self.url)
+
+class CDNPrefix(HpcCoreBase):
+    prefix = models.CharField(max_length=200, help_text="Registered Prefix for Domain")
+    contentProvider = models.ForeignKey(ContentProvider)
+    description = models.TextField(max_length=254,null=True, blank=True,help_text="Description of Content Provider")
+
+    defaultOriginServer = models.ForeignKey(OriginServer)
+    enabled = models.BooleanField(default=True)
+
+    def __unicode__(self):  return u'%s' % (self.prefix)
+
diff --git a/planetstack/hpc/tests.py b/planetstack/hpc/tests.py
new file mode 100644 (file)
index 0000000..501deb7
--- /dev/null
@@ -0,0 +1,16 @@
+"""
+This file demonstrates writing tests using the unittest module. These will pass
+when you run "manage.py test".
+
+Replace this with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+
+class SimpleTest(TestCase):
+    def test_basic_addition(self):
+        """
+        Tests that 1 + 1 always equals 2.
+        """
+        self.assertEqual(1 + 1, 2)
diff --git a/planetstack/hpc/views.py b/planetstack/hpc/views.py
new file mode 100644 (file)
index 0000000..60f00ef
--- /dev/null
@@ -0,0 +1 @@
+# Create your views here.
index 91be3dc..7593650 100644 (file)
@@ -1,3 +1,5 @@
+from django.conf.global_settings import TEMPLATE_CONTEXT_PROCESSORS as TCP
+
 # Django settings for planetstack project.
 from config import Config
 config = Config()
@@ -25,6 +27,7 @@ DATABASES = {
 
 AUTH_USER_MODEL = 'core.User'
 
+
 # Hosts/domain names that are valid for this site; required if DEBUG is False
 # See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
 ALLOWED_HOSTS = []
@@ -72,7 +75,7 @@ STATIC_ROOT = ''
 STATIC_URL = '/static/'
 
 # Additional locations of static files
-STATICFILES_DIRS = (
+STATICFILES_DIRS = ( "/opt/planetstack/core/static/",
     # Put strings here, like "/home/html/static" or "C:/www/django/static".
     # Always use forward slashes, even on Windows.
     # Don't forget to use absolute paths, not relative paths.
@@ -126,6 +129,7 @@ INSTALLED_APPS = (
     'django.contrib.messages',
     'django.contrib.staticfiles',
     # Uncomment the next line to enable the admin:
+    'suit',
     'django.contrib.admin',
     # Uncomment the next line to enable admin documentation:
     'django.contrib.admindocs',
@@ -133,9 +137,47 @@ INSTALLED_APPS = (
     'django_extensions',
     'django_evolution',
     'core',
-    'geoposition'
+    'hpc',
+    'geoposition',
 )
 
+
+# Added for django-suit form 
+TEMPLATE_CONTEXT_PROCESSORS = TCP + (
+    'django.core.context_processors.request',
+)
+
+# Django Suit configuration example
+SUIT_CONFIG = {
+    # header
+    'ADMIN_NAME': 'PlanetStack',
+    # 'HEADER_DATE_FORMAT': 'l, j. F Y',
+    # 'HEADER_TIME_FORMAT': 'H:i',
+
+    # forms
+    #'SHOW_REQUIRED_ASTERISK': True,  # Default True
+    'CONFIRM_UNSAVED_CHANGES': True, # Default True
+
+    # menu
+    # 'SEARCH_URL': '/admin/auth/user/',
+    # 'MENU_ICONS': {
+    #    'sites': 'icon-leaf',
+    #    'auth': 'icon-lock',
+    # },
+    # 'MENU_OPEN_FIRST_CHILD': True, # Default True
+    'MENU_EXCLUDE': ('auth.group','auth'),
+    'MENU': (
+    ),
+    #     'sites',
+    #     {'app': 'auth', 'icon':'icon-lock', 'models': ('user', 'group')},
+    #     {'label': 'Settings', 'icon':'icon-cog', 'models': ('auth.user', 'auth.group')},
+    #     {'label': 'Support', 'icon':'icon-question-sign', 'url': '/support/'},
+    # ),
+
+    # misc
+    # 'LIST_PER_PAGE': 15
+}
+
 # A sample logging configuration. The only tangible logging
 # performed by this configuration is to send an email to
 # the site admins on every HTTP 500 error when DEBUG=False.
index 638e3b1..039c206 100644 (file)
@@ -2,16 +2,21 @@ from django.conf.urls import patterns, include, url
 
 # Uncomment the next two lines to enable the admin:
 from django.contrib import admin
+from core.views.deployment import DeploymentList, DeploymentDetail
+from core.views.images import ImageList, ImageDetail
+from core.views.nodes import NodeList, NodeDetail
+from core.views.projects import ProjectList, ProjectDetail
+from core.views.reservations import ReservationList, ReservationDetail
 from core.views.roles import RoleList, RoleDetail
+from core.views.serviceclasses import ServiceClassList, ServiceClassDetail
+from core.views.serviceresources import ServiceResourceList, ServiceResourceDetail
 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.slice_privileges import SlicePrivilegeList, SlicePrivilegeDetail
 from core.views.slivers import SliverList, SliverDetail
-from core.views.deployment_networks import DeploymentList, DeploymentDetail
-from core.views.images import ImageList, ImageDetail
-from core.views.nodes import NodeList, NodeDetail
+from core.views.tags import TagList, TagDetail
+from core.views.users import UserList, UserDetail
 from core.models import *
 from core.api_root import api_root
 from rest_framework import generics
@@ -31,36 +36,51 @@ urlpatterns = patterns('',
 
     url(r'^plstackapi/$', api_root),
     
+    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/$', ImageList.as_view(), name='image-list'),
+    url(r'^plstackapi/images/(?P<pk>[a-zA-Z0-9_\-]+)/$', ImageDetail.as_view(), name='image-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/projects/$', ProjectList.as_view(), name='project-list'),
+    url(r'^plstackapi/projects/(?P<pk>[a-zA-Z0-9_\-]+)/$', ProjectDetail.as_view(), name='project-detail'),
+    
+    url(r'^plstackapi/reservations/$', ReservationList.as_view(), name='reservation-list'),
+    url(r'^plstackapi/reservations/(?P<pk>[a-zA-Z0-9_\-]+)/$', ReservationDetail.as_view(), name='reservation-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/$', 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/serviceclasses/$', ServiceClassList.as_view(), name='serviceclass-list'),
+    url(r'^plstackapi/serviceclasses/(?P<pk>[a-zA-Z0-9]+)/$', ServiceClassDetail.as_view(), name='serviceclass-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/serviceresources/$', ServiceResourceList.as_view(), name='serviceresource-list'),
+    url(r'^plstackapi/serviceresources/(?P<pk>[a-zA-Z0-9]+)/$', ServiceResourceDetail.as_view(), name='serviceresource-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/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/slices/$', SliceList.as_view(), name='slice-list'),
 
     url(r'^plstackapi/slices/(?P<pk>[a-zA-Z0-9_\-]+)/$', SliceDetail.as_view(), name='slice-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/slice_memberships/$', SlicePrivilegeList.as_view(), name='sliceprivilege-list'),
+    url(r'^plstackapi/slice_memberships/(?P<pk>[0-9]+)/$', SlicePrivilegeDetail.as_view(), name='sliceprivilege-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/$', 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/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/tags/$', TagList.as_view(), name='tag-list'),
+    url(r'^plstackapi/tags/(?P<pk>[a-zA-Z0-9_\-]+)/$', TagDetail.as_view(), name='tag-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'),
+    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'),
 
     #Adding in rest_framework urls
     url(r'^plstackapi/', include('rest_framework.urls', namespace='rest_framework')),
diff --git a/planetstack/templates/admin/base.html b/planetstack/templates/admin/base.html
new file mode 100644 (file)
index 0000000..477e941
--- /dev/null
@@ -0,0 +1,212 @@
+{% load admin_static %}{% load suit_tags %}{% load url from future %}<!DOCTYPE html>
+<html lang="{{ LANGUAGE_CODE|default:"en-us" }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %}>
+<head>
+  <title>{% block title %}{{ title }} | {{ 'ADMIN_NAME'|suit_conf }}{% endblock %}</title>
+  <link rel="stylesheet" type="text/css" href="{% block stylesheet %}{% endblock %}"/>
+  <link rel="stylesheet" type="text/css" href="{% static 'suit/bootstrap/css/bootstrap.min.css' %}" media="all"/>
+  <link rel="stylesheet" type="text/css" href="{% static 'suit/css/suit.css' %}" media="all">
+  <link rel="stylesheet" type="text/css" href="{% static 'planetstack.css' %}" media="all">
+  {% block extrastyle %}{% endblock %}
+  {% if LANGUAGE_BIDI %}<link rel="stylesheet" type="text/css" href="{% block stylesheet_rtl %}{% static "admin/css/rtl.css" %}{% endblock %}"/>{% endif %}
+  <script type="text/javascript">window.__admin_media_prefix__ = "{% filter escapejs %}{% static "admin/" %}{% endfilter %}";</script>
+  <script src="{% static 'suit/js/jquery-1.8.3.min.js' %}"></script>
+  <script type="text/javascript">var Suit = { $: $.noConflict() }; if (!$) $ = Suit.$; </script>
+  {% if 'SHOW_REQUIRED_ASTERISK'|suit_conf %}
+  <style type="text/css">.required:after { content: '*'; margin: 0 0 0 5px; position: absolute; color: #ccc;}</style>
+  {% endif %}
+  {% block extrahead %}{% endblock %}
+  {% block blockbots %}
+    <meta name="robots" content="NONE,NOARCHIVE"/>{% endblock %}
+</head>
+{% load i18n %}
+
+<body class="{% if is_popup %}popup {% endif %}{% block bodyclass %}{% endblock %}">
+
+<!-- Sticky footer wrap -->
+<div id="wrap">
+
+  <!-- Container -->
+  {% block container %}
+    <div id="container">
+
+      {% block header %}
+        {% if not is_popup %}
+          <!-- Header -->
+          <div id="header" class="header">
+            <div id="branding">
+              <a href="{% url 'admin:index' %}"><h1 id="site-name">{% block branding %}{{ 'ADMIN_NAME'|suit_conf }}{% endblock %}</h1></a>
+            </div>
+
+            {% block header_time %}
+            <div class="header-content header-content-first">
+              <div class="header-column icon">
+                <i class="icon-time"></i>
+              </div>
+              <div class="header-column">
+                <span class="date"> {% suit_date %}</span><br>
+                <span class="time" id="clock">{% suit_time %}</span>
+              </div>
+            </div>
+            {% endblock %}
+
+            {% block header_content %}
+              <!--<div class="header-content">
+                <div class="header-column icon">
+                  <i class="icon-comment"></i>
+                </div>
+                <div class="header-column">
+                  <a href="" class="grey"><b>2</b> new messages</a>
+                </div>
+              </div>-->
+            {% endblock %}
+
+            {% if user.is_active and user.is_staff %}
+              <div id="user-tools">
+                {% trans 'Welcome,' %}
+                <strong>
+                  {% filter force_escape %}
+                    {% firstof user.first_name user.username %}{% endfilter %}</strong>.
+                <span class="user-links">
+                {% block userlinks %}
+                  {% url 'django-admindocs-docroot' as docsroot %}
+                  {% if docsroot %}
+                    <a href="{{ docsroot }}">{% trans 'Documentation' %}</a>
+                   <span class="separator">|</span>
+                  {% endif %}
+                  <a href="{% url 'admin:password_change' %}">{% trans 'Change password' %}</a>
+                  <span class="separator">|</span>
+                  <a href="{% url 'admin:logout' %}">{% trans 'Log out' %}</a>
+                  </span>
+                {% endblock %}
+              </div>
+            {% endif %}
+            {% block nav-global %}{% endblock %}
+          </div>
+        {% endif %}
+        <!-- END Header -->
+      {% endblock %}
+
+
+      <div class="suit-columns {{ is_popup|yesno:'one-column,two-columns' }}">
+
+        {% block content-center %}
+          <div id="suit-center" class="suit-column">
+
+            {% if not is_popup %}
+              {% block breadcrumbs %}
+                <ul class="breadcrumb">
+                  <li><a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
+                    {% if title %}
+                      <span class="divider">&raquo;</span>
+                      </li>
+                      <li class="active">
+                      {{ title }}
+                    {% endif %}
+                    </li>
+                </ul>
+              {% endblock %}
+            {% endif %}
+
+            {% block messages %}
+              {% if messages %}
+
+                {% for message in messages %}
+                  <div class="alert alert-{% firstof message.tags 'info' %}">
+                    <button class="close" data-dismiss="alert">×</button>
+                    <strong>
+                      {% if message.tags %}{{ message.tags|capfirst }}{% else %}
+                        Message{% endif %}!</strong>
+                    {{ message }}
+                  </div>
+                {% endfor %}
+              {% endif %}
+            {% endblock messages %}
+
+            <!-- Content -->
+            <div id="content" class="{% block coltype %}colM{% endblock %} row-fluid">
+              {% block pretitle %}{% endblock %}
+              {% block content_title %}{% if title %}
+                <h2 class="content-title">{{ title }}</h2>
+              {% endif %}{% endblock %}
+              {% block content %}
+                {% block object-tools %}{% endblock %}
+                {{ content }}
+              {% endblock %}
+              {% block sidebar_content %}
+                {% block sidebar %}{% endblock %}
+              {% endblock %}
+            </div>
+            <!-- END Content -->
+          </div>
+        {% endblock %}
+
+
+        {% block content-left %}
+          {% if not is_popup %}
+            <div id="suit-left" class="suit-column">
+              {% block quick-search %}
+                {% with 'SEARCH_URL'|suit_conf as search_url %}
+                  {% if search_url %}
+                    <form class="form-search nav-quick-search" autocomplete="off" action="{% if '/' in search_url %}{{ search_url }}{% else %}{% url search_url %}{% endif %}" method="GET">
+                      <input type="text" name="q" class="input-medium search-query" id="quick-search">
+                      <i class="input-icon icon-search"></i>
+                      <input type="submit" class="submit" value="">
+                    </form>
+                  {% endif %}
+                {% endwith %}
+              {% endblock %}
+
+              {% include 'suit/menu.html' %}
+
+            </div>
+          {% endif %}
+        {% endblock %}
+
+      </div>
+    </div>
+  {% endblock %}
+
+  {% if not is_popup %}
+  <!-- Sticky footer push -->
+  <div id="push"></div>
+  {% endif %}
+
+</div>
+
+{% block footer %}
+  {% if not is_popup %}
+  <div id="footer" class="footer">
+    <div class="content">
+      <div class="tools">
+        {% block footer_links %}
+          <a href="http://djangosuit.com/support/" target="_blank" class="icon"><i class="icon-question-sign"></i>Support</a>
+          <a href="http://djangosuit.com/pricing/" target="_blank" class="icon"><i class="icon-bookmark"></i>Licence</a>
+          <a href="http://github.com/darklow/django-suit/issues" target="_blank" class="icon"><i class="icon-comment"></i>Report a bug</a>
+        {% endblock %}
+      </div>
+
+      <div class="copyright">
+        {% block copyright %}
+          Copyright &copy; 2013 DjangoSuit.com<br>Developed by <a href="http://djangosuit.com" target="_blank">DjangoSuit.com</a>
+        {% endblock %}
+      </div>
+
+      <div class="branding">{% block footer_branding %}
+        {% with 'ADMIN_NAME'|suit_conf as admin_name %}
+          {{ admin_name }}
+          {% if admin_name == 'Django Suit' %}
+            v{{ 'VERSION'|suit_conf }}
+          {% endif %}
+        {% endwith %}
+      {% endblock %}</div>
+    </div>
+  </div>
+  {% endif %}
+{% endblock %}
+
+  <script src="{% static 'suit/bootstrap/js/bootstrap.min.js' %}"></script>
+  <script src="{% static 'suit/js/suit.js' %}"></script>
+  {% block extrajs %}{% endblock %}
+
+</body>
+</html>
index 2bd6c82..eae91f6 100644 (file)
@@ -1,18 +1,57 @@
 {% extends "admin/base.html" %}
-{% load i18n %}
-{% block title %}{{ title }} | {% trans 'PlanetStack' %}{% endblock %}
-{% block extrastyle %}
-<style>
-#header{ background-color: #333940; border-bottom: solid 3px #999; }
-#branding h1{ color: #fff; }
-.module h2, .module caption, .inline-group h2 { background:#ccc url(/admin_media/img/admin/nav-bg.gif) bottom left repeat-x; color: #333940; }
-a.section:link, a.section:visited { color: #666666; }
-</style>
-{% endblock %}
+{% load admin_static %}
+
+{# Additional <head> content here, some extra meta tags or favicon #}
+{#{% block extrahead %}#}
+{#{% endblock %}#}
+
+
+{# Additional CSS includes #}
+{# {% block extrastyle %} #}
+{# {% endblock %} #}
+
+
+{# Additional JS files in footer, right before </body> #}
+{#{% block extrajs %}#}
+{#  <script type="text/javascript" src="{% static 'js/my_project.js' %}"></script>#}
+{#{% endblock %}#}
 
-{% block branding %}
-<h1 id="site-name">{% trans 'PlanetStack Administration' %}</h1>
+
+{# Footer links (left side) #}
+{#{% block footer_links %}#}
+{#  <a href="/docs/" class="icon"><i class="icon-question-sign"></i>Documentation</a>#}
+{#{% endblock %}#}
+
+{# Additional header content like notifications or language switcher #}
+{#{% block header_content %}#}
+{#    {{ block.super }}#}
+{#    <div class="header-content">#}
+{#        <!-- First icon column -->#}
+{#        <div class="header-column icon">#}
+{#            <i class="icon-home"></i><br>#}
+{#            <i class="icon-cog"></i>#}
+{#        </div>#}
+{#        <div class="header-column" style="margin-right: 20px">#}
+{#            <a href="/" class="grey">Front-end</a><br>#}
+{#            <a href="" class="grey">One more link</a>#}
+{#        </div>#}
+{#        <!-- Second icon column -->#}
+{#        <div class="header-column icon">#}
+{#            <i class="icon-comment"></i>#}
+{#        </div>#}
+{#        <div class="header-column">#}
+{#            <a href="" class="grey">5 new messages</a>#}
+{#        </div>#}
+{#    </div>#}
+{#{% endblock %}#}
+
+{# Footer branding name (center) #}
+{% block footer_branding %}
+PlanetStack
 {% endblock %}
 
-{% block nav-global %}{% endblock %}
+
+{# Footer copyright (right side) #}
+{% block copyright %}
+{#  Copyright &copy; 2013 Client<br>Developed by <a href="http://yoursite.com" target="_blank">YourName</a> #}
+{% endblock %}