1 from core.models import Site
2 from core.models import *
3 from openstack.manager import OpenStackManager
5 from django.contrib import admin
6 from django.contrib.auth.models import Group
7 from django import forms
8 from django.utils.safestring import mark_safe
9 from django.contrib.auth.admin import UserAdmin
10 from django.contrib.admin.widgets import FilteredSelectMultiple
11 from django.contrib.auth.forms import ReadOnlyPasswordHashField
12 from django.contrib.auth.signals import user_logged_in
13 from django.utils import timezone
14 from django.contrib.contenttypes import generic
15 from suit.widgets import LinkedSelect
16 from django.core.exceptions import PermissionDenied
17 from django.core.urlresolvers import reverse, NoReverseMatch
19 import django_evolution
21 class ReadOnlyAwareAdmin(admin.ModelAdmin):
23 def has_add_permission(self, request, obj=None):
24 return (not self.__user_is_readonly(request))
26 def has_delete_permission(self, request, obj=None):
27 return (not self.__user_is_readonly(request))
29 def save_model(self, request, obj, form, change):
30 if self.__user_is_readonly(request):
31 raise PermissionDenied
34 return super(ReadOnlyAwareAdmin, self).save_model(request, obj, form, change)
36 def get_actions(self,request):
37 actions = super(ReadOnlyAwareAdmin,self).get_actions(request)
39 if self.__user_is_readonly(request):
40 if 'delete_selected' in actions:
41 del actions['delete_selected']
45 def change_view(self,request,object_id, extra_context=None):
46 if self.__user_is_readonly(request):
47 if not hasattr(self, "readonly_save"):
\r
48 # save the original readonly fields
\r
49 self.readonly_save = self.readonly_fields
\r
50 self.inlines_save = self.inlines
\r
51 if hasattr(self, "user_readonly_fields"):
\r
52 self.readonly_fields=self.user_readonly_fields
\r
53 if hasattr(self, "user_readonly_inlines"):
\r
54 self.inlines = self.user_readonly_inlines
\r
56 if hasattr(self, "readonly_save"):
\r
57 # restore the original readonly fields
\r
58 self.readonly_fields = self.readonly_save
\r
59 if hasattr(self, "inlines_save"):
\r
60 self.inlines = self.inlines_save
63 return super(ReadOnlyAwareAdmin, self).change_view(request, object_id, extra_context=extra_context)
64 except PermissionDenied:
66 if request.method == 'POST':
67 raise PermissionDenied
68 request.readonly = True
69 return super(ReadOnlyAwareAdmin, self).change_view(request, object_id, extra_context=extra_context)
71 def __user_is_readonly(self, request):
72 return request.user.isReadOnlyUser()
74 class SingletonAdmin (ReadOnlyAwareAdmin):
75 def has_add_permission(self, request):
76 if not super(SingletonAdmin, self).has_add_permission(request):
79 num_objects = self.model.objects.count()
86 class PlStackTabularInline(admin.TabularInline):
87 def __init__(self, *args, **kwargs):
88 super(PlStackTabularInline, self).__init__(*args, **kwargs)
90 # InlineModelAdmin as no get_fields() method, so in order to add
91 # the selflink field, we override __init__ to modify self.fields and
92 # self.readonly_fields.
96 def get_change_url(self, model, id):
97 """ Get the URL to a change form in the admin for this model """
98 reverse_path = "admin:%s_change" % (model._meta.db_table)
100 url = reverse(reverse_path, args=(id,))
101 except NoReverseMatch:
106 def setup_selflink(self):
107 if hasattr(self, "selflink_fieldname"):
108 """ self.selflink_model can be defined to punch through a relation
109 to its target object. For example, in SliceNetworkInline, set
110 selflink_model = "network", and the URL will lead to the Network
111 object instead of trying to bring up a change view of the
114 self.selflink_model = getattr(self.model,self.selflink_fieldname).field.rel.to
116 self.selflink_model = self.model
118 url = self.get_change_url(self.selflink_model, 0)
120 # We don't have an admin for this object, so don't create the
125 # Since we need to add "selflink" to the field list, we need to create
126 # self.fields if it is None.
127 if (self.fields is None):
129 for f in self.model._meta.fields:
130 if f.editable and f.name != "id":
131 self.fields.append(f.name)
133 self.fields = tuple(self.fields) + ("selflink", )
135 if self.readonly_fields is None:
136 self.readonly_fields = ()
138 self.readonly_fields = tuple(self.readonly_fields) + ("selflink", )
140 def selflink(self, obj):
141 if hasattr(self, "selflink_fieldname"):
142 obj = getattr(obj, self.selflink_fieldname)
145 url = self.get_change_url(self.selflink_model, obj.id)
146 return "<a href='%s'>Details</a>" % str(url)
148 return "Not present"
\r
150 selflink.allow_tags = True
151 selflink.short_description = "Details"
153 class ReadOnlyTabularInline(PlStackTabularInline):
156 def get_readonly_fields(self, request, obj=None):
159 def has_add_permission(self, request):
162 class ReservationROInline(ReadOnlyTabularInline):
165 suit_classes = 'suit-tab suit-tab-reservations'
166 fields = ['startTime','slice','duration']
168 class ReservationInline(PlStackTabularInline):
171 suit_classes = 'suit-tab suit-tab-reservations'
173 def queryset(self, request):
174 return Reservation.select_by_user(request.user)
176 class TagROInline(generic.GenericTabularInline):
179 suit_classes = 'suit-tab suit-tab-tags'
181 fields = ['service', 'name', 'value']
183 def get_readonly_fields(self, request, obj=None):
186 def has_add_permission(self, request):
190 class TagInline(generic.GenericTabularInline):
193 suit_classes = 'suit-tab suit-tab-tags'
194 fields = ['service', 'name', 'value']
196 def queryset(self, request):
197 return Tag.select_by_user(request.user)
199 class NetworkLookerUpper:
200 """ This is a callable that looks up a network name in a sliver and returns
201 the ip address for that network.
204 def __init__(self, name):
205 self.short_description = name
207 self.network_name = name
209 def __call__(self, obj):
211 for nbs in obj.networksliver_set.all():
212 if (nbs.network.name == self.network_name):
217 return self.network_name
219 class SliverROInline(ReadOnlyTabularInline):
221 fields = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']
222 suit_classes = 'suit-tab suit-tab-slivers'
224 class SliverInline(PlStackTabularInline):
226 fields = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']
228 readonly_fields = ['ip', 'instance_name']
229 suit_classes = 'suit-tab suit-tab-slivers'
231 def queryset(self, request):
232 return Sliver.select_by_user(request.user)
234 # Note this is breaking in the admin.py when trying to use an inline to add a node/image
235 # def _declared_fieldsets(self):
236 # # Return None so django will call get_fieldsets and we can insert our
240 # def get_readonly_fields(self, request, obj=None):
241 # readonly_fields = super(SliverInline, self).get_readonly_fields(request, obj)
243 # # Lookup the networks that are bound to the slivers, and add those
244 # # network names to the list of readonly fields.
246 # for sliver in obj.slivers.all():
247 # for nbs in sliver.networksliver_set.all():
249 # network_name = nbs.network.name
250 # if network_name not in [str(x) for x in readonly_fields]:
251 # readonly_fields.append(NetworkLookerUpper(network_name))
253 # return readonly_fields
255 # def get_fieldsets(self, request, obj=None):
256 # form = self.get_formset(request, obj).form
257 # # fields = the read/write files + the read-only fields
258 # fields = self.fields
259 # for fieldName in self.get_readonly_fields(request,obj):
260 # if not fieldName in fields:
261 # fields.append(fieldName)
263 # return [(None, {'fields': fields})]
267 class SiteROInline(ReadOnlyTabularInline):
270 fields = ['name', 'login_base', 'site_url', 'enabled']
271 suit_classes = 'suit-tab suit-tab-sites'
273 class SiteInline(PlStackTabularInline):
276 suit_classes = 'suit-tab suit-tab-sites'
278 def queryset(self, request):
279 return Site.select_by_user(request.user)
281 class UserROInline(ReadOnlyTabularInline):
283 fields = ['email', 'firstname', 'lastname']
285 suit_classes = 'suit-tab suit-tab-users'
287 class UserInline(PlStackTabularInline):
289 fields = ['email', 'firstname', 'lastname']
291 suit_classes = 'suit-tab suit-tab-users'
293 def queryset(self, request):
294 return User.select_by_user(request.user)
296 class SliceROInline(ReadOnlyTabularInline):
298 suit_classes = 'suit-tab suit-tab-slices'
299 fields = ['name','site', 'serviceClass', 'service']
301 class SliceInline(PlStackTabularInline):
303 fields = ['name','site', 'serviceClass', 'service']
305 suit_classes = 'suit-tab suit-tab-slices'
307 def queryset(self, request):
308 return Slice.select_by_user(request.user)
310 class NodeROInline(ReadOnlyTabularInline):
313 suit_classes = 'suit-tab suit-tab-nodes'
314 fields = ['name','deployment']
316 class NodeInline(PlStackTabularInline):
319 suit_classes = 'suit-tab suit-tab-nodes'
320 fields = ['name','deployment']
322 class DeploymentPrivilegeROInline(ReadOnlyTabularInline):
323 model = DeploymentPrivilege
325 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
326 fields = ['user','role','deployment']
328 class DeploymentPrivilegeInline(PlStackTabularInline):
329 model = DeploymentPrivilege
331 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
332 fields = ['user','role','deployment']
334 def queryset(self, request):
335 return DeploymentPrivilege.select_by_user(request.user)
337 #CLEANUP DOUBLE SitePrivilegeInline
338 class SitePrivilegeROInline(ReadOnlyTabularInline):
339 model = SitePrivilege
341 suit_classes = 'suit-tab suit-tab-siteprivileges'
342 fields = ['user','site', 'role']
344 class SitePrivilegeInline(PlStackTabularInline):
345 model = SitePrivilege
347 suit_classes = 'suit-tab suit-tab-siteprivileges'
348 fields = ['user','site', 'role']
350 def formfield_for_foreignkey(self, db_field, request, **kwargs):
351 if db_field.name == 'site':
352 kwargs['queryset'] = Site.select_by_user(request.user)
354 if db_field.name == 'user':
355 kwargs['queryset'] = User.select_by_user(request.user)
356 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
358 def queryset(self, request):
359 return SitePrivilege.select_by_user(request.user)
361 class SiteDeploymentROInline(ReadOnlyTabularInline):
362 model = SiteDeployments
363 #model = Site.deployments.through
365 suit_classes = 'suit-tab suit-tab-deployments'
366 fields = ['deployment','site']
368 class SiteDeploymentInline(PlStackTabularInline):
369 model = SiteDeployments
370 #model = Site.deployments.through
372 suit_classes = 'suit-tab suit-tab-deployments'
373 fields = ['deployment','site']
375 def formfield_for_foreignkey(self, db_field, request, **kwargs):
376 if db_field.name == 'site':
377 kwargs['queryset'] = Site.select_by_user(request.user)
379 if db_field.name == 'deployment':
380 kwargs['queryset'] = Deployment.select_by_user(request.user)
381 return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
383 def queryset(self, request):
384 return SiteDeployments.select_by_user(request.user)
387 class SlicePrivilegeROInline(ReadOnlyTabularInline):
388 model = SlicePrivilege
390 suit_classes = 'suit-tab suit-tab-sliceprivileges'
391 fields = ['user', 'slice', 'role']
393 class SlicePrivilegeInline(PlStackTabularInline):
394 model = SlicePrivilege
395 suit_classes = 'suit-tab suit-tab-sliceprivileges'
397 fields = ('user', 'slice','role')
399 def formfield_for_foreignkey(self, db_field, request, **kwargs):
400 if db_field.name == 'slice':
401 kwargs['queryset'] = Slice.select_by_user(request.user)
402 if db_field.name == 'user':
403 kwargs['queryset'] = User.select_by_user(request.user)
405 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
407 def queryset(self, request):
408 return SlicePrivilege.select_by_user(request.user)
410 class SliceNetworkROInline(ReadOnlyTabularInline):
411 model = Network.slices.through
413 verbose_name = "Network Connection"
414 verbose_name_plural = "Network Connections"
415 suit_classes = 'suit-tab suit-tab-slicenetworks'
418 class SliceNetworkInline(PlStackTabularInline):
419 model = Network.slices.through
420 selflink_fieldname = "network"
422 verbose_name = "Network Connection"
423 verbose_name_plural = "Network Connections"
424 suit_classes = 'suit-tab suit-tab-slicenetworks'
427 class ImageDeploymentsInline(PlStackTabularInline):
428 model = ImageDeployments
430 verbose_name = "Image Deployments"
431 verbose_name_plural = "Image Deployments"
432 suit_classes = 'suit-tab suit-tab-imagedeployments'
433 fields = ['deployment', 'glance_image_id']
434 readonly_fields = ['deployment', 'glance_image_id']
436 class PlainTextWidget(forms.HiddenInput):
437 input_type = 'hidden'
439 def render(self, name, value, attrs=None):
442 return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
444 class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
447 def save_model(self, request, obj, form, change):
448 obj.caller = request.user
449 # update openstack connection to use this site/tenant
450 obj.save_by_user(request.user)
452 def delete_model(self, request, obj):
453 obj.delete_by_user(request.user)
455 def save_formset(self, request, form, formset, change):
456 instances = formset.save(commit=False)
457 for instance in instances:
458 instance.save_by_user(request.user)
461 class SliceRoleAdmin(PlanetStackBaseAdmin):
465 class SiteRoleAdmin(PlanetStackBaseAdmin):
469 class DeploymentAdminForm(forms.ModelForm):
470 sites = forms.ModelMultipleChoiceField(
471 queryset=Site.objects.all(),
473 widget=FilteredSelectMultiple(
474 verbose_name=('Sites'), is_stacked=False
480 def __init__(self, *args, **kwargs):
481 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
483 if self.instance and self.instance.pk:
484 self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments_set.all()]
486 def save(self, commit=True):
487 deployment = super(DeploymentAdminForm, self).save(commit=False)
493 # save_m2m() doesn't seem to work with 'through' relations. So we
494 # create/destroy the through models ourselves. There has to be
497 sites = self.cleaned_data['sites']
500 for sdp in list(deployment.sitedeployments_set.all()):
501 if sdp.site not in sites:
502 #print "deleting site", sdp.site
505 existing_sites.append(sdp.site)
508 if site not in existing_sites:
509 #print "adding site", site
510 sdp = SiteDeployments(site=site, deployment=deployment)
517 class DeploymentAdminROForm(DeploymentAdminForm):
518 def save(self, commit=True):
519 raise PermissionDenied
521 class SiteAssocInline(PlStackTabularInline):
522 model = Site.deployments.through
524 suit_classes = 'suit-tab suit-tab-sites'
526 class DeploymentAdmin(PlanetStackBaseAdmin):
527 #form = DeploymentAdminForm
529 fieldList = ['name','sites']
530 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
531 inlines = [DeploymentPrivilegeInline,NodeInline,TagInline]
533 user_readonly_inlines = [DeploymentPrivilegeROInline,NodeROInline,TagROInline]
534 user_readonly_fields = ['name']
536 suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags'))
538 def get_form(self, request, obj=None, **kwargs):
539 if request.user.isReadOnlyUser():
540 kwargs["form"] = DeploymentAdminROForm
542 kwargs["form"] = DeploymentAdminForm
543 return super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
545 class ServiceAttrAsTabROInline(ReadOnlyTabularInline):
546 model = ServiceAttribute
547 fields = ['name','value']
549 suit_classes = 'suit-tab suit-tab-serviceattrs'
551 class ServiceAttrAsTabInline(PlStackTabularInline):
552 model = ServiceAttribute
553 fields = ['name','value']
555 suit_classes = 'suit-tab suit-tab-serviceattrs'
557 class ServiceAdmin(PlanetStackBaseAdmin):
558 list_display = ("name","description","versionNumber","enabled","published")
559 fieldList = ["name","description","versionNumber","enabled","published"]
560 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
561 inlines = [ServiceAttrAsTabInline,SliceInline]
563 user_readonly_fields = fieldList
564 user_readonly_inlines = [ServiceAttrAsTabROInline,SliceROInline]
566 suit_form_tabs =(('general', 'Service Details'),
568 ('serviceattrs','Additional Attributes'),
571 class SiteAdmin(PlanetStackBaseAdmin):
572 fieldList = ['name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
574 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
575 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
577 suit_form_tabs =(('general', 'Site Details'),
579 ('siteprivileges','Privileges'),
580 ('deployments','Deployments'),
585 readonly_fields = ['accountLink']
587 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
588 user_readonly_inlines = [SliceROInline,UserROInline,TagROInline, NodeROInline, SitePrivilegeROInline,SiteDeploymentROInline]
590 list_display = ('name', 'login_base','site_url', 'enabled')
591 filter_horizontal = ('deployments',)
592 inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentInline]
593 search_fields = ['name']
595 def queryset(self, request):
596 return Site.select_by_user(request.user)
598 def get_formsets(self, request, obj=None):
599 for inline in self.get_inline_instances(request, obj):
600 # hide MyInline in the add view
603 if isinstance(inline, SliceInline):
604 inline.model.caller = request.user
605 yield inline.get_formset(request, obj)
607 def get_formsets(self, request, obj=None):
608 for inline in self.get_inline_instances(request, obj):
609 # hide MyInline in the add view
612 if isinstance(inline, SliverInline):
613 inline.model.caller = request.user
614 yield inline.get_formset(request, obj)
616 def accountLink(self, obj):
617 link_obj = obj.accounts.all()
619 reverse_path = "admin:core_account_change"
620 url = reverse(reverse_path, args =(link_obj[0].id,))
621 return "<a href='%s'>%s</a>" % (url, "view billing details")
623 return "no billing data for this site"
624 accountLink.allow_tags = True
625 accountLink.short_description = "Billing"
627 def save_model(self, request, obj, form, change):
628 # update openstack connection to use this site/tenant
629 obj.save_by_user(request.user)
631 def delete_model(self, request, obj):
632 obj.delete_by_user(request.user)
635 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
636 fieldList = ['user', 'site', 'role']
638 (None, {'fields': fieldList, 'classes':['collapse']})
640 list_display = ('user', 'site', 'role')
641 user_readonly_fields = fieldList
642 user_readonly_inlines = []
644 def formfield_for_foreignkey(self, db_field, request, **kwargs):
645 if db_field.name == 'site':
646 if not request.user.is_admin:
647 # only show sites where user is an admin or pi
649 for site_privilege in SitePrivilege.objects.filer(user=request.user):
650 if site_privilege.role.role_type in ['admin', 'pi']:
651 sites.add(site_privilege.site)
652 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
654 if db_field.name == 'user':
655 if not request.user.is_admin:
656 # only show users from sites where caller has admin or pi role
657 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
658 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
659 sites = [site_privilege.site for site_privilege in site_privileges]
660 site_privileges = SitePrivilege.objects.filter(site__in=sites)
661 emails = [site_privilege.user.email for site_privilege in site_privileges]
662 users = User.objects.filter(email__in=emails)
663 kwargs['queryset'] = users
665 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
667 def queryset(self, request):
668 # admins can see all privileges. Users can only see privileges at sites
669 # where they have the admin role or pi role.
670 qs = super(SitePrivilegeAdmin, self).queryset(request)
671 #if not request.user.is_admin:
672 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
673 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
674 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
675 # sites = Site.objects.filter(login_base__in=login_bases)
676 # qs = qs.filter(site__in=sites)
679 class SliceForm(forms.ModelForm):
683 'service': LinkedSelect
686 class SliceAdmin(PlanetStackBaseAdmin):
688 fieldList = ['name', 'site', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
689 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
690 list_display = ('name', 'site','serviceClass', 'slice_url', 'max_slivers')
691 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
693 user_readonly_fields = fieldList
694 user_readonly_inlines = [SlicePrivilegeROInline,SliverROInline,TagROInline, ReservationROInline, SliceNetworkROInline]
696 suit_form_tabs =(('general', 'Slice Details'),
697 ('slicenetworks','Networks'),
698 ('sliceprivileges','Privileges'),
699 ('slivers','Slivers'),
701 ('reservations','Reservations'),
704 def formfield_for_foreignkey(self, db_field, request, **kwargs):
705 if db_field.name == 'site':
706 kwargs['queryset'] = Site.select_by_user(request.user)
708 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
710 def queryset(self, request):
711 # admins can see all keys. Users can only see slices they belong to.
712 return Slice.select_by_user(request.user)
714 def get_formsets(self, request, obj=None):
715 for inline in self.get_inline_instances(request, obj):
716 # hide MyInline in the add view
719 if isinstance(inline, SliverInline):
720 inline.model.caller = request.user
721 yield inline.get_formset(request, obj)
724 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
726 (None, {'fields': ['user', 'slice', 'role']})
728 list_display = ('user', 'slice', 'role')
730 user_readonly_fields = ['user', 'slice', 'role']
731 user_readonly_inlines = []
733 def formfield_for_foreignkey(self, db_field, request, **kwargs):
734 if db_field.name == 'slice':
735 kwargs['queryset'] = Slice.select_by_user(request.user)
737 if db_field.name == 'user':
738 kwargs['queryset'] = User.select_by_user(request.user)
740 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
742 def queryset(self, request):
743 # admins can see all memberships. Users can only see memberships of
744 # slices where they have the admin role.
745 return SlicePrivilege.select_by_user(request.user)
747 def save_model(self, request, obj, form, change):
748 # update openstack connection to use this site/tenant
749 auth = request.session.get('auth', {})
750 auth['tenant'] = obj.slice.name
751 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
754 def delete_model(self, request, obj):
755 # update openstack connection to use this site/tenant
756 auth = request.session.get('auth', {})
757 auth['tenant'] = obj.slice.name
758 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
762 class ImageAdmin(PlanetStackBaseAdmin):
764 fieldsets = [('Image Details',
765 {'fields': ['name', 'disk_format', 'container_format'],
766 'classes': ['suit-tab suit-tab-general']})
769 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
771 inlines = [SliverInline, ImageDeploymentsInline]
773 user_readonly_fields = ['name', 'disk_format', 'container_format']
774 user_readonly_inlines = [SliverROInline]
776 class NodeForm(forms.ModelForm):
779 'site': LinkedSelect,
780 'deployment': LinkedSelect
783 class NodeAdmin(PlanetStackBaseAdmin):
785 list_display = ('name', 'site', 'deployment')
786 list_filter = ('deployment',)
788 inlines = [TagInline,SliverInline]
789 fieldsets = [('Node Details', {'fields': ['name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
791 user_readonly_fields = ['name','site','deployment']
792 user_readonly_inlines = [TagInline,SliverInline]
794 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
797 class SliverForm(forms.ModelForm):
800 ip = forms.CharField(widget=PlainTextWidget)
801 instance_name = forms.CharField(widget=PlainTextWidget)
803 'ip': PlainTextWidget(),
804 'instance_name': PlainTextWidget(),
805 'slice': LinkedSelect,
806 'deploymentNetwork': LinkedSelect,
807 'node': LinkedSelect,
808 'image': LinkedSelect
811 class TagAdmin(PlanetStackBaseAdmin):
812 list_display = ['service', 'name', 'value', 'content_type', 'content_object',]
813 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
814 user_readonly_inlines = []
816 class SliverAdmin(PlanetStackBaseAdmin):
819 ('Sliver Details', {'fields': ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
821 list_display = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']
823 suit_form_tabs =(('general', 'Sliver Details'),
827 inlines = [TagInline]
829 user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image']
830 user_readonly_inlines = [TagROInline]
832 def formfield_for_foreignkey(self, db_field, request, **kwargs):
833 if db_field.name == 'slice':
834 kwargs['queryset'] = Slice.select_by_user(request.user)
836 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
838 def queryset(self, request):
839 # admins can see all slivers. Users can only see slivers of
840 # the slices they belong to.
841 return Sliver.select_by_user(request.user)
844 def get_formsets(self, request, obj=None):
845 # make some fields read only if we are updating an existing record
847 #self.readonly_fields = ('ip', 'instance_name')
848 self.readonly_fields = ()
850 self.readonly_fields = ()
851 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
853 for inline in self.get_inline_instances(request, obj):
854 # hide MyInline in the add view
857 if isinstance(inline, SliverInline):
858 inline.model.caller = request.user
859 yield inline.get_formset(request, obj)
861 #def save_model(self, request, obj, form, change):
862 # # update openstack connection to use this site/tenant
863 # auth = request.session.get('auth', {})
864 # auth['tenant'] = obj.slice.name
865 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
866 # obj.creator = request.user
869 #def delete_model(self, request, obj):
870 # # update openstack connection to use this site/tenant
871 # auth = request.session.get('auth', {})
872 # auth['tenant'] = obj.slice.name
873 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
876 class UserCreationForm(forms.ModelForm):
877 """A form for creating new users. Includes all the required
878 fields, plus a repeated password."""
879 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
880 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
884 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
886 def clean_password2(self):
887 # Check that the two password entries match
888 password1 = self.cleaned_data.get("password1")
889 password2 = self.cleaned_data.get("password2")
890 if password1 and password2 and password1 != password2:
891 raise forms.ValidationError("Passwords don't match")
894 def save(self, commit=True):
895 # Save the provided password in hashed format
896 user = super(UserCreationForm, self).save(commit=False)
897 user.password = self.cleaned_data["password1"]
898 #user.set_password(self.cleaned_data["password1"])
904 class UserChangeForm(forms.ModelForm):
905 """A form for updating users. Includes all the fields on
906 the user, but replaces the password field with admin's
907 password hash display field.
909 password = ReadOnlyPasswordHashField(label='Password',
910 help_text= '<a href=\"password/\">Change Password</a>.')
915 def clean_password(self):
916 # Regardless of what the user provides, return the initial value.
917 # This is done here, rather than on the field, because the
918 # field does not have access to the initial value
919 return self.initial["password"]
921 class UserDashboardViewInline(PlStackTabularInline):
922 model = UserDashboardView
924 suit_classes = 'suit-tab suit-tab-dashboards'
925 fields = ['user', 'dashboardView', 'order']
927 class UserDashboardViewROInline(ReadOnlyTabularInline):
928 model = UserDashboardView
930 suit_classes = 'suit-tab suit-tab-dashboards'
931 fields = ['user', 'dashboardView', 'order']
933 class UserAdmin(UserAdmin):
937 # The forms to add and change user instances
938 form = UserChangeForm
939 add_form = UserCreationForm
941 # The fields to be used in displaying the User model.
942 # These override the definitions on the base UserAdmin
943 # that reference specific fields on auth.User.
944 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
945 #list_display = ('email', 'username','firstname', 'lastname', 'is_admin', 'last_login')
946 list_filter = ('site',)
947 inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
949 fieldListLoginDetails = ['email','site','password','is_readonly','is_amin','public_key']
950 fieldListContactInfo = ['firstname','lastname','phone','timezone']
953 ('Login Details', {'fields': ['email', 'site','password', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
954 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
955 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
956 #('Important dates', {'fields': ('last_login',)}),
960 'classes': ('wide',),
961 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
964 search_fields = ('email',)
965 ordering = ('email',)
966 filter_horizontal = ()
968 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
969 user_readonly_inlines = [SlicePrivilegeROInline,SitePrivilegeROInline,DeploymentPrivilegeROInline,UserDashboardViewROInline]
971 suit_form_tabs =(('general','Login Details'),
972 ('contact','Contact Information'),
973 ('sliceprivileges','Slice Privileges'),
974 ('siteprivileges','Site Privileges'),
975 ('deploymentprivileges','Deployment Privileges'),
976 ('dashboards','Dashboard Views'))
978 def formfield_for_foreignkey(self, db_field, request, **kwargs):
979 if db_field.name == 'site':
980 kwargs['queryset'] = Site.select_by_user(request.user)
982 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
984 def has_add_permission(self, request, obj=None):
985 return (not self.__user_is_readonly(request))
987 def has_delete_permission(self, request, obj=None):
988 return (not self.__user_is_readonly(request))
990 def get_actions(self,request):
991 actions = super(UserAdmin,self).get_actions(request)
993 if self.__user_is_readonly(request):
994 if 'delete_selected' in actions:
995 del actions['delete_selected']
999 def change_view(self,request,object_id, extra_context=None):
1001 if self.__user_is_readonly(request):
1002 if not hasattr(self, "readonly_save"):
1003 # save the original readonly fields
\r
1004 self.readonly_save = self.readonly_fields
\r
1005 self.inlines_save = self.inlines
1006 self.readonly_fields=self.user_readonly_fields
1007 self.inlines = self.user_readonly_inlines
1009 if hasattr(self, "readonly_save"):
\r
1010 # restore the original readonly fields
\r
1011 self.readonly_fields = self.readonly_save
\r
1012 self.inlines = self.inlines_save
1015 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1016 except PermissionDenied:
1018 if request.method == 'POST':
1019 raise PermissionDenied
1020 request.readonly = True
1021 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1023 def __user_is_readonly(self, request):
1024 #groups = [x.name for x in request.user.groups.all() ]
1025 #return "readonly" in groups
1026 return request.user.isReadOnlyUser()
1028 def queryset(self, request):
1029 return User.select_by_user(request.user)
1031 class DashboardViewAdmin(PlanetStackBaseAdmin):
1032 fieldsets = [('Dashboard View Details',
1033 {'fields': ['name', 'url'],
1034 'classes': ['suit-tab suit-tab-general']})
1037 suit_form_tabs =(('general','Dashboard View Details'),)
1039 class ServiceResourceROInline(ReadOnlyTabularInline):
1040 model = ServiceResource
1042 fields = ['serviceClass', 'name', 'maxUnitsDeployment', 'maxUnitsNode', 'maxDuration', 'bucketInRate', 'bucketMaxSize', 'cost', 'calendarReservable']
1044 class ServiceResourceInline(PlStackTabularInline):
1045 model = ServiceResource
1048 class ServiceClassAdmin(PlanetStackBaseAdmin):
1049 list_display = ('name', 'commitment', 'membershipFee')
1050 inlines = [ServiceResourceInline]
1052 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1053 user_readonly_inlines = []
1055 class ReservedResourceROInline(ReadOnlyTabularInline):
1056 model = ReservedResource
1058 fields = ['sliver', 'resource','quantity','reservationSet']
1059 suit_classes = 'suit-tab suit-tab-reservedresources'
1061 class ReservedResourceInline(PlStackTabularInline):
1062 model = ReservedResource
1064 suit_classes = 'suit-tab suit-tab-reservedresources'
1066 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1067 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1069 if db_field.name == 'resource':
1070 # restrict resources to those that the slice's service class allows
1071 if request._slice is not None:
1072 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1073 if len(field.queryset) > 0:
1074 field.initial = field.queryset.all()[0]
1076 field.queryset = field.queryset.none()
\r
1077 elif db_field.name == 'sliver':
\r
1078 # restrict slivers to those that belong to the slice
\r
1079 if request._slice is not None:
\r
1080 field.queryset = field.queryset.filter(slice = request._slice)
1082 field.queryset = field.queryset.none()
\r
1086 def queryset(self, request):
1087 return ReservedResource.select_by_user(request.user)
1089 class ReservationChangeForm(forms.ModelForm):
1093 'slice' : LinkedSelect
1096 class ReservationAddForm(forms.ModelForm):
1097 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1098 refresh = forms.CharField(widget=forms.HiddenInput())
1101 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1103 def clean_slice(self):
1104 slice = self.cleaned_data.get("slice")
1105 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1107 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1113 'slice' : LinkedSelect
1117 class ReservationAddRefreshForm(ReservationAddForm):
1118 """ This form is displayed when the Reservation Form receives an update
1119 from the Slice dropdown onChange handler. It doesn't validate the
1120 data and doesn't save the data. This will cause the form to be
1124 """ don't validate anything other than slice """
1125 dont_validate_fields = ("startTime", "duration")
1127 def full_clean(self):
1128 result = super(ReservationAddForm, self).full_clean()
1130 for fieldname in self.dont_validate_fields:
1131 if fieldname in self._errors:
1132 del self._errors[fieldname]
1136 """ don't save anything """
1140 class ReservationAdmin(PlanetStackBaseAdmin):
1141 fieldList = ['slice', 'startTime', 'duration']
1142 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1143 list_display = ('startTime', 'duration')
1144 form = ReservationAddForm
1146 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1148 inlines = [ReservedResourceInline]
1149 user_readonly_inlines = [ReservedResourceROInline]
1150 user_readonly_fields = fieldList
1152 def add_view(self, request, form_url='', extra_context=None):
1153 timezone.activate(request.user.timezone)
1154 request._refresh = False
1155 request._slice = None
1156 if request.method == 'POST':
1157 # "refresh" will be set to "1" if the form was submitted due to
1158 # a change in the Slice dropdown.
1159 if request.POST.get("refresh","1") == "1":
1160 request._refresh = True
1161 request.POST["refresh"] = "0"
1163 # Keep track of the slice that was selected, so the
1164 # reservedResource inline can filter items for the slice.
1165 request._slice = request.POST.get("slice",None)
1166 if (request._slice is not None):
1167 request._slice = Slice.objects.get(id=request._slice)
1169 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1172 def changelist_view(self, request, extra_context = None):
1173 timezone.activate(request.user.timezone)
1174 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1176 def get_form(self, request, obj=None, **kwargs):
1179 # For changes, set request._slice to the slice already set in the
1181 request._slice = obj.slice
1182 self.form = ReservationChangeForm
1184 if getattr(request, "_refresh", False):
1185 self.form = ReservationAddRefreshForm
1187 self.form = ReservationAddForm
1188 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1190 def get_readonly_fields(self, request, obj=None):
1191 if (obj is not None):
1192 # Prevent slice from being changed after the reservation has been
1198 def queryset(self, request):
1199 return Reservation.select_by_user(request.user)
1201 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1202 list_display = ("name", )
1203 user_readonly_fields = ['name']
1204 user_readonly_inlines = []
1206 class RouterAdmin(PlanetStackBaseAdmin):
1207 list_display = ("name", )
1208 user_readonly_fields = ['name']
1209 user_readonly_inlines = []
1211 class RouterROInline(ReadOnlyTabularInline):
1212 model = Router.networks.through
1214 verbose_name_plural = "Routers"
1215 verbose_name = "Router"
1216 suit_classes = 'suit-tab suit-tab-routers'
1218 fields = ['name', 'owner', 'permittedNetworks', 'networks']
1220 class RouterInline(PlStackTabularInline):
1221 model = Router.networks.through
1223 verbose_name_plural = "Routers"
1224 verbose_name = "Router"
1225 suit_classes = 'suit-tab suit-tab-routers'
1227 class NetworkParameterROInline(ReadOnlyTabularInline):
1228 model = NetworkParameter
1230 verbose_name_plural = "Parameters"
1231 verbose_name = "Parameter"
1232 suit_classes = 'suit-tab suit-tab-netparams'
1233 fields = ['parameter', 'value', 'content_type', 'object_id', 'content_object']
1235 class NetworkParameterInline(generic.GenericTabularInline):
1236 model = NetworkParameter
1238 verbose_name_plural = "Parameters"
1239 verbose_name = "Parameter"
1240 suit_classes = 'suit-tab suit-tab-netparams'
1242 class NetworkSliversROInline(ReadOnlyTabularInline):
1243 fields = ['network', 'sliver', 'ip', 'port_id']
1244 model = NetworkSliver
1246 verbose_name_plural = "Slivers"
1247 verbose_name = "Sliver"
1248 suit_classes = 'suit-tab suit-tab-networkslivers'
1250 class NetworkSliversInline(PlStackTabularInline):
1251 readonly_fields = ("ip", )
1252 model = NetworkSliver
1253 selflink_fieldname = "sliver"
1255 verbose_name_plural = "Slivers"
1256 verbose_name = "Sliver"
1257 suit_classes = 'suit-tab suit-tab-networkslivers'
1259 class NetworkSlicesROInline(ReadOnlyTabularInline):
1260 model = NetworkSlice
1262 verbose_name_plural = "Slices"
1263 verbose_name = "Slice"
1264 suit_classes = 'suit-tab suit-tab-networkslices'
1265 fields = ['network','slice']
1267 class NetworkSlicesInline(PlStackTabularInline):
1268 model = NetworkSlice
1269 selflink_fieldname = "slice"
1271 verbose_name_plural = "Slices"
1272 verbose_name = "Slice"
1273 suit_classes = 'suit-tab suit-tab-networkslices'
1275 class NetworkAdmin(PlanetStackBaseAdmin):
1276 list_display = ("name", "subnet", "ports", "labels")
1277 readonly_fields = ("subnet", )
1279 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1282 (None, {'fields': ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet'], 'classes':['suit-tab suit-tab-general']}),]
1284 user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1285 user_readonly_inlines = [NetworkParameterROInline, NetworkSliversROInline, NetworkSlicesROInline, RouterROInline]
1288 ('general','Network Details'),
1289 ('netparams', 'Parameters'),
1290 ('networkslivers','Slivers'),
1291 ('networkslices','Slices'),
1292 ('routers','Routers'),
1294 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1295 list_display = ("name", "guaranteedBandwidth", "visibility")
1296 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1297 user_readonly_inlines = []
1299 # register a signal that caches the user's credentials when they log in
1300 def cache_credentials(sender, user, request, **kwds):
1301 auth = {'username': request.POST['username'],
1302 'password': request.POST['password']}
1303 request.session['auth'] = auth
1304 user_logged_in.connect(cache_credentials)
1306 def dollar_field(fieldName, short_description):
1307 def newFunc(self, obj):
1309 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1311 x=getattr(obj, fieldName, 0.0)
1313 newFunc.short_description = short_description
1316 def right_dollar_field(fieldName, short_description):
1317 def newFunc(self, obj):
1319 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1320 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1322 x=getattr(obj, fieldName, 0.0)
1324 newFunc.short_description = short_description
1325 newFunc.allow_tags = True
1328 class InvoiceChargeInline(PlStackTabularInline):
1331 verbose_name_plural = "Charges"
1332 verbose_name = "Charge"
1333 exclude = ['account']
1334 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1335 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1339 dollar_amount = right_dollar_field("amount", "Amount")
1341 class InvoiceAdmin(admin.ModelAdmin):
1342 list_display = ("date", "account")
1344 inlines = [InvoiceChargeInline]
1346 fields = ["date", "account", "dollar_amount"]
1347 readonly_fields = ["date", "account", "dollar_amount"]
1349 dollar_amount = dollar_field("amount", "Amount")
1351 class InvoiceInline(PlStackTabularInline):
1354 verbose_name_plural = "Invoices"
1355 verbose_name = "Invoice"
1356 fields = ["date", "dollar_amount"]
1357 readonly_fields = ["date", "dollar_amount"]
1358 suit_classes = 'suit-tab suit-tab-accountinvoice'
1362 dollar_amount = right_dollar_field("amount", "Amount")
1364 class PendingChargeInline(PlStackTabularInline):
1367 verbose_name_plural = "Charges"
1368 verbose_name = "Charge"
1369 exclude = ["invoice"]
1370 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1371 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1372 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1376 def queryset(self, request):
1377 qs = super(PendingChargeInline, self).queryset(request)
1378 qs = qs.filter(state="pending")
1381 dollar_amount = right_dollar_field("amount", "Amount")
1383 class PaymentInline(PlStackTabularInline):
1386 verbose_name_plural = "Payments"
1387 verbose_name = "Payment"
1388 fields = ["date", "dollar_amount"]
1389 readonly_fields = ["date", "dollar_amount"]
1390 suit_classes = 'suit-tab suit-tab-accountpayments'
1394 dollar_amount = right_dollar_field("amount", "Amount")
1396 class AccountAdmin(admin.ModelAdmin):
1397 list_display = ("site", "balance_due")
1399 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1402 (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1404 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1407 ('general','Account Details'),
1408 ('accountinvoice', 'Invoices'),
1409 ('accountpayments', 'Payments'),
1410 ('accountpendingcharges','Pending Charges'),
1413 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1414 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1415 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1418 # Now register the new UserAdmin...
1419 admin.site.register(User, UserAdmin)
1420 # ... and, since we're not using Django's builtin permissions,
1421 # unregister the Group model from admin.
1422 #admin.site.unregister(Group)
1424 #Do not show django evolution in the admin interface
1425 from django_evolution.models import Version, Evolution
1426 #admin.site.unregister(Version)
1427 #admin.site.unregister(Evolution)
1430 # When debugging it is often easier to see all the classes, but for regular use
1431 # only the top-levels should be displayed
1434 admin.site.register(Deployment, DeploymentAdmin)
1435 admin.site.register(Site, SiteAdmin)
1436 admin.site.register(Slice, SliceAdmin)
1437 admin.site.register(Service, ServiceAdmin)
1438 admin.site.register(Reservation, ReservationAdmin)
1439 admin.site.register(Network, NetworkAdmin)
1440 admin.site.register(Router, RouterAdmin)
1441 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1442 admin.site.register(Account, AccountAdmin)
1443 admin.site.register(Invoice, InvoiceAdmin)
1446 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1447 admin.site.register(ServiceClass, ServiceClassAdmin)
1448 #admin.site.register(PlanetStack)
1449 admin.site.register(Tag, TagAdmin)
1450 admin.site.register(DeploymentRole)
1451 admin.site.register(SiteRole)
1452 admin.site.register(SliceRole)
1453 admin.site.register(PlanetStackRole)
1454 admin.site.register(Node, NodeAdmin)
1455 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1456 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1457 admin.site.register(Sliver, SliverAdmin)
1458 admin.site.register(Image, ImageAdmin)
1459 admin.site.register(DashboardView, DashboardViewAdmin)