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 request = kwargs.pop('request', None)
482 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
484 self.fields['accessControl'].initial = "allow site " + request.user.site.name
486 if self.instance and self.instance.pk:
487 self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments_set.all()]
489 def save(self, commit=True):
490 deployment = super(DeploymentAdminForm, self).save(commit=False)
496 # save_m2m() doesn't seem to work with 'through' relations. So we
497 # create/destroy the through models ourselves. There has to be
500 sites = self.cleaned_data['sites']
503 for sdp in list(deployment.sitedeployments_set.all()):
504 if sdp.site not in sites:
505 #print "deleting site", sdp.site
508 existing_sites.append(sdp.site)
511 if site not in existing_sites:
512 #print "adding site", site
513 sdp = SiteDeployments(site=site, deployment=deployment)
520 class DeploymentAdminROForm(DeploymentAdminForm):
521 def save(self, commit=True):
522 raise PermissionDenied
524 class SiteAssocInline(PlStackTabularInline):
525 model = Site.deployments.through
527 suit_classes = 'suit-tab suit-tab-sites'
529 class DeploymentAdmin(PlanetStackBaseAdmin):
531 fieldList = ['name','sites', 'accessControl']
532 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
533 inlines = [DeploymentPrivilegeInline,NodeInline,TagInline]
535 user_readonly_inlines = [DeploymentPrivilegeROInline,NodeROInline,TagROInline]
536 user_readonly_fields = ['name']
538 suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags'))
540 def get_form(self, request, obj=None, **kwargs):
541 if request.user.isReadOnlyUser():
542 kwargs["form"] = DeploymentAdminROForm
544 kwargs["form"] = DeploymentAdminForm
545 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
547 # from stackexchange: pass the request object into the form
549 class AdminFormMetaClass(adminForm):
550 def __new__(cls, *args, **kwargs):
551 kwargs['request'] = request
552 return adminForm(*args, **kwargs)
554 return AdminFormMetaClass
556 class ServiceAttrAsTabROInline(ReadOnlyTabularInline):
557 model = ServiceAttribute
558 fields = ['name','value']
560 suit_classes = 'suit-tab suit-tab-serviceattrs'
562 class ServiceAttrAsTabInline(PlStackTabularInline):
563 model = ServiceAttribute
564 fields = ['name','value']
566 suit_classes = 'suit-tab suit-tab-serviceattrs'
568 class ServiceAdmin(PlanetStackBaseAdmin):
569 list_display = ("name","description","versionNumber","enabled","published")
570 fieldList = ["name","description","versionNumber","enabled","published"]
571 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
572 inlines = [ServiceAttrAsTabInline,SliceInline]
574 user_readonly_fields = fieldList
575 user_readonly_inlines = [ServiceAttrAsTabROInline,SliceROInline]
577 suit_form_tabs =(('general', 'Service Details'),
579 ('serviceattrs','Additional Attributes'),
582 class SiteAdmin(PlanetStackBaseAdmin):
583 fieldList = ['name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
585 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
586 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
588 suit_form_tabs =(('general', 'Site Details'),
590 ('siteprivileges','Privileges'),
591 ('deployments','Deployments'),
596 readonly_fields = ['accountLink']
598 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
599 user_readonly_inlines = [SliceROInline,UserROInline,TagROInline, NodeROInline, SitePrivilegeROInline,SiteDeploymentROInline]
601 list_display = ('name', 'login_base','site_url', 'enabled')
602 filter_horizontal = ('deployments',)
603 inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentInline]
604 search_fields = ['name']
606 def queryset(self, request):
607 return Site.select_by_user(request.user)
609 def get_formsets(self, request, obj=None):
610 for inline in self.get_inline_instances(request, obj):
611 # hide MyInline in the add view
614 if isinstance(inline, SliceInline):
615 inline.model.caller = request.user
616 yield inline.get_formset(request, obj)
618 def get_formsets(self, request, obj=None):
619 for inline in self.get_inline_instances(request, obj):
620 # hide MyInline in the add view
623 if isinstance(inline, SliverInline):
624 inline.model.caller = request.user
625 yield inline.get_formset(request, obj)
627 def accountLink(self, obj):
628 link_obj = obj.accounts.all()
630 reverse_path = "admin:core_account_change"
631 url = reverse(reverse_path, args =(link_obj[0].id,))
632 return "<a href='%s'>%s</a>" % (url, "view billing details")
634 return "no billing data for this site"
635 accountLink.allow_tags = True
636 accountLink.short_description = "Billing"
638 def save_model(self, request, obj, form, change):
639 # update openstack connection to use this site/tenant
640 obj.save_by_user(request.user)
642 def delete_model(self, request, obj):
643 obj.delete_by_user(request.user)
646 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
647 fieldList = ['user', 'site', 'role']
649 (None, {'fields': fieldList, 'classes':['collapse']})
651 list_display = ('user', 'site', 'role')
652 user_readonly_fields = fieldList
653 user_readonly_inlines = []
655 def formfield_for_foreignkey(self, db_field, request, **kwargs):
656 if db_field.name == 'site':
657 if not request.user.is_admin:
658 # only show sites where user is an admin or pi
660 for site_privilege in SitePrivilege.objects.filer(user=request.user):
661 if site_privilege.role.role_type in ['admin', 'pi']:
662 sites.add(site_privilege.site)
663 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
665 if db_field.name == 'user':
666 if not request.user.is_admin:
667 # only show users from sites where caller has admin or pi role
668 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
669 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
670 sites = [site_privilege.site for site_privilege in site_privileges]
671 site_privileges = SitePrivilege.objects.filter(site__in=sites)
672 emails = [site_privilege.user.email for site_privilege in site_privileges]
673 users = User.objects.filter(email__in=emails)
674 kwargs['queryset'] = users
676 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
678 def queryset(self, request):
679 # admins can see all privileges. Users can only see privileges at sites
680 # where they have the admin role or pi role.
681 qs = super(SitePrivilegeAdmin, self).queryset(request)
682 #if not request.user.is_admin:
683 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
684 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
685 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
686 # sites = Site.objects.filter(login_base__in=login_bases)
687 # qs = qs.filter(site__in=sites)
690 class SliceForm(forms.ModelForm):
694 'service': LinkedSelect
697 class SliceAdmin(PlanetStackBaseAdmin):
699 fieldList = ['name', 'site', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
700 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
701 list_display = ('name', 'site','serviceClass', 'slice_url', 'max_slivers')
702 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
704 user_readonly_fields = fieldList
705 user_readonly_inlines = [SlicePrivilegeROInline,SliverROInline,TagROInline, ReservationROInline, SliceNetworkROInline]
707 suit_form_tabs =(('general', 'Slice Details'),
708 ('slicenetworks','Networks'),
709 ('sliceprivileges','Privileges'),
710 ('slivers','Slivers'),
712 ('reservations','Reservations'),
715 def formfield_for_foreignkey(self, db_field, request, **kwargs):
716 if db_field.name == 'site':
717 kwargs['queryset'] = Site.select_by_user(request.user)
719 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
721 def queryset(self, request):
722 # admins can see all keys. Users can only see slices they belong to.
723 return Slice.select_by_user(request.user)
725 def get_formsets(self, request, obj=None):
726 for inline in self.get_inline_instances(request, obj):
727 # hide MyInline in the add view
730 if isinstance(inline, SliverInline):
731 inline.model.caller = request.user
732 yield inline.get_formset(request, obj)
735 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
737 (None, {'fields': ['user', 'slice', 'role']})
739 list_display = ('user', 'slice', 'role')
741 user_readonly_fields = ['user', 'slice', 'role']
742 user_readonly_inlines = []
744 def formfield_for_foreignkey(self, db_field, request, **kwargs):
745 if db_field.name == 'slice':
746 kwargs['queryset'] = Slice.select_by_user(request.user)
748 if db_field.name == 'user':
749 kwargs['queryset'] = User.select_by_user(request.user)
751 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
753 def queryset(self, request):
754 # admins can see all memberships. Users can only see memberships of
755 # slices where they have the admin role.
756 return SlicePrivilege.select_by_user(request.user)
758 def save_model(self, request, obj, form, change):
759 # update openstack connection to use this site/tenant
760 auth = request.session.get('auth', {})
761 auth['tenant'] = obj.slice.name
762 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
765 def delete_model(self, request, obj):
766 # update openstack connection to use this site/tenant
767 auth = request.session.get('auth', {})
768 auth['tenant'] = obj.slice.name
769 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
773 class ImageAdmin(PlanetStackBaseAdmin):
775 fieldsets = [('Image Details',
776 {'fields': ['name', 'disk_format', 'container_format'],
777 'classes': ['suit-tab suit-tab-general']})
780 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
782 inlines = [SliverInline, ImageDeploymentsInline]
784 user_readonly_fields = ['name', 'disk_format', 'container_format']
785 user_readonly_inlines = [SliverROInline]
787 class NodeForm(forms.ModelForm):
790 'site': LinkedSelect,
791 'deployment': LinkedSelect
794 class NodeAdmin(PlanetStackBaseAdmin):
796 list_display = ('name', 'site', 'deployment')
797 list_filter = ('deployment',)
799 inlines = [TagInline,SliverInline]
800 fieldsets = [('Node Details', {'fields': ['name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
802 user_readonly_fields = ['name','site','deployment']
803 user_readonly_inlines = [TagInline,SliverInline]
805 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
808 class SliverForm(forms.ModelForm):
811 ip = forms.CharField(widget=PlainTextWidget)
812 instance_name = forms.CharField(widget=PlainTextWidget)
814 'ip': PlainTextWidget(),
815 'instance_name': PlainTextWidget(),
816 'slice': LinkedSelect,
817 'deploymentNetwork': LinkedSelect,
818 'node': LinkedSelect,
819 'image': LinkedSelect
822 class TagAdmin(PlanetStackBaseAdmin):
823 list_display = ['service', 'name', 'value', 'content_type', 'content_object',]
824 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
825 user_readonly_inlines = []
827 class SliverAdmin(PlanetStackBaseAdmin):
830 ('Sliver Details', {'fields': ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
832 list_display = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']
834 suit_form_tabs =(('general', 'Sliver Details'),
838 inlines = [TagInline]
840 user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image']
841 user_readonly_inlines = [TagROInline]
843 def formfield_for_foreignkey(self, db_field, request, **kwargs):
844 if db_field.name == 'slice':
845 kwargs['queryset'] = Slice.select_by_user(request.user)
847 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
849 def queryset(self, request):
850 # admins can see all slivers. Users can only see slivers of
851 # the slices they belong to.
852 return Sliver.select_by_user(request.user)
855 def get_formsets(self, request, obj=None):
856 # make some fields read only if we are updating an existing record
858 #self.readonly_fields = ('ip', 'instance_name')
859 self.readonly_fields = ()
861 self.readonly_fields = ()
862 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
864 for inline in self.get_inline_instances(request, obj):
865 # hide MyInline in the add view
868 if isinstance(inline, SliverInline):
869 inline.model.caller = request.user
870 yield inline.get_formset(request, obj)
872 #def save_model(self, request, obj, form, change):
873 # # update openstack connection to use this site/tenant
874 # auth = request.session.get('auth', {})
875 # auth['tenant'] = obj.slice.name
876 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
877 # obj.creator = request.user
880 #def delete_model(self, request, obj):
881 # # update openstack connection to use this site/tenant
882 # auth = request.session.get('auth', {})
883 # auth['tenant'] = obj.slice.name
884 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
887 class UserCreationForm(forms.ModelForm):
888 """A form for creating new users. Includes all the required
889 fields, plus a repeated password."""
890 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
891 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
895 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
897 def clean_password2(self):
898 # Check that the two password entries match
899 password1 = self.cleaned_data.get("password1")
900 password2 = self.cleaned_data.get("password2")
901 if password1 and password2 and password1 != password2:
902 raise forms.ValidationError("Passwords don't match")
905 def save(self, commit=True):
906 # Save the provided password in hashed format
907 user = super(UserCreationForm, self).save(commit=False)
908 user.password = self.cleaned_data["password1"]
909 #user.set_password(self.cleaned_data["password1"])
915 class UserChangeForm(forms.ModelForm):
916 """A form for updating users. Includes all the fields on
917 the user, but replaces the password field with admin's
918 password hash display field.
920 password = ReadOnlyPasswordHashField(label='Password',
921 help_text= '<a href=\"password/\">Change Password</a>.')
926 def clean_password(self):
927 # Regardless of what the user provides, return the initial value.
928 # This is done here, rather than on the field, because the
929 # field does not have access to the initial value
930 return self.initial["password"]
932 class UserDashboardViewInline(PlStackTabularInline):
933 model = UserDashboardView
935 suit_classes = 'suit-tab suit-tab-dashboards'
936 fields = ['user', 'dashboardView', 'order']
938 class UserDashboardViewROInline(ReadOnlyTabularInline):
939 model = UserDashboardView
941 suit_classes = 'suit-tab suit-tab-dashboards'
942 fields = ['user', 'dashboardView', 'order']
944 class UserAdmin(UserAdmin):
948 # The forms to add and change user instances
949 form = UserChangeForm
950 add_form = UserCreationForm
952 # The fields to be used in displaying the User model.
953 # These override the definitions on the base UserAdmin
954 # that reference specific fields on auth.User.
955 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
956 #list_display = ('email', 'username','firstname', 'lastname', 'is_admin', 'last_login')
957 list_filter = ('site',)
958 inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
960 fieldListLoginDetails = ['email','site','password','is_readonly','is_amin','public_key']
961 fieldListContactInfo = ['firstname','lastname','phone','timezone']
964 ('Login Details', {'fields': ['email', 'site','password', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
965 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
966 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
967 #('Important dates', {'fields': ('last_login',)}),
971 'classes': ('wide',),
972 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
975 search_fields = ('email',)
976 ordering = ('email',)
977 filter_horizontal = ()
979 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
980 user_readonly_inlines = [SlicePrivilegeROInline,SitePrivilegeROInline,DeploymentPrivilegeROInline,UserDashboardViewROInline]
982 suit_form_tabs =(('general','Login Details'),
983 ('contact','Contact Information'),
984 ('sliceprivileges','Slice Privileges'),
985 ('siteprivileges','Site Privileges'),
986 ('deploymentprivileges','Deployment Privileges'),
987 ('dashboards','Dashboard Views'))
989 def formfield_for_foreignkey(self, db_field, request, **kwargs):
990 if db_field.name == 'site':
991 kwargs['queryset'] = Site.select_by_user(request.user)
993 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
995 def has_add_permission(self, request, obj=None):
996 return (not self.__user_is_readonly(request))
998 def has_delete_permission(self, request, obj=None):
999 return (not self.__user_is_readonly(request))
1001 def get_actions(self,request):
1002 actions = super(UserAdmin,self).get_actions(request)
1004 if self.__user_is_readonly(request):
1005 if 'delete_selected' in actions:
1006 del actions['delete_selected']
1010 def change_view(self,request,object_id, extra_context=None):
1012 if self.__user_is_readonly(request):
1013 if not hasattr(self, "readonly_save"):
1014 # save the original readonly fields
\r
1015 self.readonly_save = self.readonly_fields
\r
1016 self.inlines_save = self.inlines
1017 self.readonly_fields=self.user_readonly_fields
1018 self.inlines = self.user_readonly_inlines
1020 if hasattr(self, "readonly_save"):
\r
1021 # restore the original readonly fields
\r
1022 self.readonly_fields = self.readonly_save
\r
1023 self.inlines = self.inlines_save
1026 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1027 except PermissionDenied:
1029 if request.method == 'POST':
1030 raise PermissionDenied
1031 request.readonly = True
1032 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1034 def __user_is_readonly(self, request):
1035 #groups = [x.name for x in request.user.groups.all() ]
1036 #return "readonly" in groups
1037 return request.user.isReadOnlyUser()
1039 def queryset(self, request):
1040 return User.select_by_user(request.user)
1042 class DashboardViewAdmin(PlanetStackBaseAdmin):
1043 fieldsets = [('Dashboard View Details',
1044 {'fields': ['name', 'url'],
1045 'classes': ['suit-tab suit-tab-general']})
1048 suit_form_tabs =(('general','Dashboard View Details'),)
1050 class ServiceResourceROInline(ReadOnlyTabularInline):
1051 model = ServiceResource
1053 fields = ['serviceClass', 'name', 'maxUnitsDeployment', 'maxUnitsNode', 'maxDuration', 'bucketInRate', 'bucketMaxSize', 'cost', 'calendarReservable']
1055 class ServiceResourceInline(PlStackTabularInline):
1056 model = ServiceResource
1059 class ServiceClassAdmin(PlanetStackBaseAdmin):
1060 list_display = ('name', 'commitment', 'membershipFee')
1061 inlines = [ServiceResourceInline]
1063 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1064 user_readonly_inlines = []
1066 class ReservedResourceROInline(ReadOnlyTabularInline):
1067 model = ReservedResource
1069 fields = ['sliver', 'resource','quantity','reservationSet']
1070 suit_classes = 'suit-tab suit-tab-reservedresources'
1072 class ReservedResourceInline(PlStackTabularInline):
1073 model = ReservedResource
1075 suit_classes = 'suit-tab suit-tab-reservedresources'
1077 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1078 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1080 if db_field.name == 'resource':
1081 # restrict resources to those that the slice's service class allows
1082 if request._slice is not None:
1083 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1084 if len(field.queryset) > 0:
1085 field.initial = field.queryset.all()[0]
1087 field.queryset = field.queryset.none()
\r
1088 elif db_field.name == 'sliver':
\r
1089 # restrict slivers to those that belong to the slice
\r
1090 if request._slice is not None:
\r
1091 field.queryset = field.queryset.filter(slice = request._slice)
1093 field.queryset = field.queryset.none()
\r
1097 def queryset(self, request):
1098 return ReservedResource.select_by_user(request.user)
1100 class ReservationChangeForm(forms.ModelForm):
1104 'slice' : LinkedSelect
1107 class ReservationAddForm(forms.ModelForm):
1108 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1109 refresh = forms.CharField(widget=forms.HiddenInput())
1112 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1114 def clean_slice(self):
1115 slice = self.cleaned_data.get("slice")
1116 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1118 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1124 'slice' : LinkedSelect
1128 class ReservationAddRefreshForm(ReservationAddForm):
1129 """ This form is displayed when the Reservation Form receives an update
1130 from the Slice dropdown onChange handler. It doesn't validate the
1131 data and doesn't save the data. This will cause the form to be
1135 """ don't validate anything other than slice """
1136 dont_validate_fields = ("startTime", "duration")
1138 def full_clean(self):
1139 result = super(ReservationAddForm, self).full_clean()
1141 for fieldname in self.dont_validate_fields:
1142 if fieldname in self._errors:
1143 del self._errors[fieldname]
1147 """ don't save anything """
1151 class ReservationAdmin(PlanetStackBaseAdmin):
1152 fieldList = ['slice', 'startTime', 'duration']
1153 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1154 list_display = ('startTime', 'duration')
1155 form = ReservationAddForm
1157 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1159 inlines = [ReservedResourceInline]
1160 user_readonly_inlines = [ReservedResourceROInline]
1161 user_readonly_fields = fieldList
1163 def add_view(self, request, form_url='', extra_context=None):
1164 timezone.activate(request.user.timezone)
1165 request._refresh = False
1166 request._slice = None
1167 if request.method == 'POST':
1168 # "refresh" will be set to "1" if the form was submitted due to
1169 # a change in the Slice dropdown.
1170 if request.POST.get("refresh","1") == "1":
1171 request._refresh = True
1172 request.POST["refresh"] = "0"
1174 # Keep track of the slice that was selected, so the
1175 # reservedResource inline can filter items for the slice.
1176 request._slice = request.POST.get("slice",None)
1177 if (request._slice is not None):
1178 request._slice = Slice.objects.get(id=request._slice)
1180 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1183 def changelist_view(self, request, extra_context = None):
1184 timezone.activate(request.user.timezone)
1185 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1187 def get_form(self, request, obj=None, **kwargs):
1190 # For changes, set request._slice to the slice already set in the
1192 request._slice = obj.slice
1193 self.form = ReservationChangeForm
1195 if getattr(request, "_refresh", False):
1196 self.form = ReservationAddRefreshForm
1198 self.form = ReservationAddForm
1199 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1201 def get_readonly_fields(self, request, obj=None):
1202 if (obj is not None):
1203 # Prevent slice from being changed after the reservation has been
1209 def queryset(self, request):
1210 return Reservation.select_by_user(request.user)
1212 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1213 list_display = ("name", )
1214 user_readonly_fields = ['name']
1215 user_readonly_inlines = []
1217 class RouterAdmin(PlanetStackBaseAdmin):
1218 list_display = ("name", )
1219 user_readonly_fields = ['name']
1220 user_readonly_inlines = []
1222 class RouterROInline(ReadOnlyTabularInline):
1223 model = Router.networks.through
1225 verbose_name_plural = "Routers"
1226 verbose_name = "Router"
1227 suit_classes = 'suit-tab suit-tab-routers'
1229 fields = ['name', 'owner', 'permittedNetworks', 'networks']
1231 class RouterInline(PlStackTabularInline):
1232 model = Router.networks.through
1234 verbose_name_plural = "Routers"
1235 verbose_name = "Router"
1236 suit_classes = 'suit-tab suit-tab-routers'
1238 class NetworkParameterROInline(ReadOnlyTabularInline):
1239 model = NetworkParameter
1241 verbose_name_plural = "Parameters"
1242 verbose_name = "Parameter"
1243 suit_classes = 'suit-tab suit-tab-netparams'
1244 fields = ['parameter', 'value', 'content_type', 'object_id', 'content_object']
1246 class NetworkParameterInline(generic.GenericTabularInline):
1247 model = NetworkParameter
1249 verbose_name_plural = "Parameters"
1250 verbose_name = "Parameter"
1251 suit_classes = 'suit-tab suit-tab-netparams'
1253 class NetworkSliversROInline(ReadOnlyTabularInline):
1254 fields = ['network', 'sliver', 'ip', 'port_id']
1255 model = NetworkSliver
1257 verbose_name_plural = "Slivers"
1258 verbose_name = "Sliver"
1259 suit_classes = 'suit-tab suit-tab-networkslivers'
1261 class NetworkSliversInline(PlStackTabularInline):
1262 readonly_fields = ("ip", )
1263 model = NetworkSliver
1264 selflink_fieldname = "sliver"
1266 verbose_name_plural = "Slivers"
1267 verbose_name = "Sliver"
1268 suit_classes = 'suit-tab suit-tab-networkslivers'
1270 class NetworkSlicesROInline(ReadOnlyTabularInline):
1271 model = NetworkSlice
1273 verbose_name_plural = "Slices"
1274 verbose_name = "Slice"
1275 suit_classes = 'suit-tab suit-tab-networkslices'
1276 fields = ['network','slice']
1278 class NetworkSlicesInline(PlStackTabularInline):
1279 model = NetworkSlice
1280 selflink_fieldname = "slice"
1282 verbose_name_plural = "Slices"
1283 verbose_name = "Slice"
1284 suit_classes = 'suit-tab suit-tab-networkslices'
1286 class NetworkAdmin(PlanetStackBaseAdmin):
1287 list_display = ("name", "subnet", "ports", "labels")
1288 readonly_fields = ("subnet", )
1290 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1293 (None, {'fields': ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet'], 'classes':['suit-tab suit-tab-general']}),]
1295 user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1296 user_readonly_inlines = [NetworkParameterROInline, NetworkSliversROInline, NetworkSlicesROInline, RouterROInline]
1299 ('general','Network Details'),
1300 ('netparams', 'Parameters'),
1301 ('networkslivers','Slivers'),
1302 ('networkslices','Slices'),
1303 ('routers','Routers'),
1305 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1306 list_display = ("name", "guaranteedBandwidth", "visibility")
1307 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1308 user_readonly_inlines = []
1310 # register a signal that caches the user's credentials when they log in
1311 def cache_credentials(sender, user, request, **kwds):
1312 auth = {'username': request.POST['username'],
1313 'password': request.POST['password']}
1314 request.session['auth'] = auth
1315 user_logged_in.connect(cache_credentials)
1317 def dollar_field(fieldName, short_description):
1318 def newFunc(self, obj):
1320 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1322 x=getattr(obj, fieldName, 0.0)
1324 newFunc.short_description = short_description
1327 def right_dollar_field(fieldName, short_description):
1328 def newFunc(self, obj):
1330 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1331 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1333 x=getattr(obj, fieldName, 0.0)
1335 newFunc.short_description = short_description
1336 newFunc.allow_tags = True
1339 class InvoiceChargeInline(PlStackTabularInline):
1342 verbose_name_plural = "Charges"
1343 verbose_name = "Charge"
1344 exclude = ['account']
1345 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1346 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1350 dollar_amount = right_dollar_field("amount", "Amount")
1352 class InvoiceAdmin(admin.ModelAdmin):
1353 list_display = ("date", "account")
1355 inlines = [InvoiceChargeInline]
1357 fields = ["date", "account", "dollar_amount"]
1358 readonly_fields = ["date", "account", "dollar_amount"]
1360 dollar_amount = dollar_field("amount", "Amount")
1362 class InvoiceInline(PlStackTabularInline):
1365 verbose_name_plural = "Invoices"
1366 verbose_name = "Invoice"
1367 fields = ["date", "dollar_amount"]
1368 readonly_fields = ["date", "dollar_amount"]
1369 suit_classes = 'suit-tab suit-tab-accountinvoice'
1373 dollar_amount = right_dollar_field("amount", "Amount")
1375 class PendingChargeInline(PlStackTabularInline):
1378 verbose_name_plural = "Charges"
1379 verbose_name = "Charge"
1380 exclude = ["invoice"]
1381 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1382 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1383 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1387 def queryset(self, request):
1388 qs = super(PendingChargeInline, self).queryset(request)
1389 qs = qs.filter(state="pending")
1392 dollar_amount = right_dollar_field("amount", "Amount")
1394 class PaymentInline(PlStackTabularInline):
1397 verbose_name_plural = "Payments"
1398 verbose_name = "Payment"
1399 fields = ["date", "dollar_amount"]
1400 readonly_fields = ["date", "dollar_amount"]
1401 suit_classes = 'suit-tab suit-tab-accountpayments'
1405 dollar_amount = right_dollar_field("amount", "Amount")
1407 class AccountAdmin(admin.ModelAdmin):
1408 list_display = ("site", "balance_due")
1410 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1413 (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1415 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1418 ('general','Account Details'),
1419 ('accountinvoice', 'Invoices'),
1420 ('accountpayments', 'Payments'),
1421 ('accountpendingcharges','Pending Charges'),
1424 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1425 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1426 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1429 # Now register the new UserAdmin...
1430 admin.site.register(User, UserAdmin)
1431 # ... and, since we're not using Django's builtin permissions,
1432 # unregister the Group model from admin.
1433 #admin.site.unregister(Group)
1435 #Do not show django evolution in the admin interface
1436 from django_evolution.models import Version, Evolution
1437 #admin.site.unregister(Version)
1438 #admin.site.unregister(Evolution)
1441 # When debugging it is often easier to see all the classes, but for regular use
1442 # only the top-levels should be displayed
1445 admin.site.register(Deployment, DeploymentAdmin)
1446 admin.site.register(Site, SiteAdmin)
1447 admin.site.register(Slice, SliceAdmin)
1448 admin.site.register(Service, ServiceAdmin)
1449 admin.site.register(Reservation, ReservationAdmin)
1450 admin.site.register(Network, NetworkAdmin)
1451 admin.site.register(Router, RouterAdmin)
1452 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1453 admin.site.register(Account, AccountAdmin)
1454 admin.site.register(Invoice, InvoiceAdmin)
1457 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1458 admin.site.register(ServiceClass, ServiceClassAdmin)
1459 #admin.site.register(PlanetStack)
1460 admin.site.register(Tag, TagAdmin)
1461 admin.site.register(DeploymentRole)
1462 admin.site.register(SiteRole)
1463 admin.site.register(SliceRole)
1464 admin.site.register(PlanetStackRole)
1465 admin.site.register(Node, NodeAdmin)
1466 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1467 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1468 admin.site.register(Sliver, SliverAdmin)
1469 admin.site.register(Image, ImageAdmin)
1470 admin.site.register(DashboardView, DashboardViewAdmin)