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 def backend_icon(value):
24 elif value == "Provisioning in progress":
25 return '<img src="/static/admin/img/icon_clock.gif">'
27 return '<img src="/static/admin/img/icon_error.gif">'
29 class PlainTextWidget(forms.HiddenInput):
32 def render(self, name, value, attrs=None):
35 return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
37 class BackendStatusIconWidget(forms.Widget):
38 def render(self, name, value, attrs=None):
39 return mark_safe('<div title="%s">%s</div>' % (value, backend_icon(value)))
41 class BackendStatusFullWidget(forms.Widget):
42 def render(self, name, value, attrs=None):
43 return mark_safe('%s %s' % (backend_icon(value), value))
45 class ReadOnlyAwareAdmin(admin.ModelAdmin):
47 def has_add_permission(self, request, obj=None):
48 return (not self.__user_is_readonly(request))
50 def has_delete_permission(self, request, obj=None):
51 return (not self.__user_is_readonly(request))
53 def save_model(self, request, obj, form, change):
54 if self.__user_is_readonly(request):
55 raise PermissionDenied
58 return super(ReadOnlyAwareAdmin, self).save_model(request, obj, form, change)
60 def get_actions(self,request):
61 actions = super(ReadOnlyAwareAdmin,self).get_actions(request)
63 if self.__user_is_readonly(request):
64 if 'delete_selected' in actions:
65 del actions['delete_selected']
69 def change_view(self,request,object_id, extra_context=None):
70 if self.__user_is_readonly(request):
71 if not hasattr(self, "readonly_save"):
\r
72 # save the original readonly fields
\r
73 self.readonly_save = self.readonly_fields
\r
74 self.inlines_save = self.inlines
\r
75 if hasattr(self, "user_readonly_fields"):
\r
76 self.readonly_fields=self.user_readonly_fields
\r
77 if hasattr(self, "user_readonly_inlines"):
\r
78 self.inlines = self.user_readonly_inlines
\r
80 if hasattr(self, "readonly_save"):
\r
81 # restore the original readonly fields
\r
82 self.readonly_fields = self.readonly_save
\r
83 if hasattr(self, "inlines_save"):
\r
84 self.inlines = self.inlines_save
87 return super(ReadOnlyAwareAdmin, self).change_view(request, object_id, extra_context=extra_context)
88 except PermissionDenied:
90 if request.method == 'POST':
91 raise PermissionDenied
92 request.readonly = True
93 return super(ReadOnlyAwareAdmin, self).change_view(request, object_id, extra_context=extra_context)
95 def __user_is_readonly(self, request):
96 return request.user.isReadOnlyUser()
98 def formfield_for_dbfield(self, db_field, **kwargs):
99 if (db_field.name == 'backend_status'):
100 kwargs['widget'] = BackendStatusFullWidget(attrs={"title": "foo"})
101 result = super(ReadOnlyAwareAdmin, self).formfield_for_dbfield(db_field, **kwargs)
103 if (db_field.name == 'backend_status'):
104 result.required = False
108 def backend_status_icon(self, obj):
109 if hasattr(obj, 'backend_status'):
110 value = obj.backend_status
111 return mark_safe('<div title="%s">%s</div>' % (value, backend_icon(value)))
114 backend_status_icon.short_description = ""
117 class SingletonAdmin (ReadOnlyAwareAdmin):
118 def has_add_permission(self, request):
119 if not super(SingletonAdmin, self).has_add_permission(request):
122 num_objects = self.model.objects.count()
129 class PlStackTabularInline(admin.TabularInline):
130 def __init__(self, *args, **kwargs):
131 super(PlStackTabularInline, self).__init__(*args, **kwargs)
133 # InlineModelAdmin as no get_fields() method, so in order to add
134 # the selflink field, we override __init__ to modify self.fields and
135 # self.readonly_fields.
137 self.setup_selflink()
139 def get_change_url(self, model, id):
140 """ Get the URL to a change form in the admin for this model """
141 reverse_path = "admin:%s_change" % (model._meta.db_table)
143 url = reverse(reverse_path, args=(id,))
144 except NoReverseMatch:
149 def setup_selflink(self):
150 if hasattr(self, "selflink_fieldname"):
151 """ self.selflink_model can be defined to punch through a relation
152 to its target object. For example, in SliceNetworkInline, set
153 selflink_model = "network", and the URL will lead to the Network
154 object instead of trying to bring up a change view of the
157 self.selflink_model = getattr(self.model,self.selflink_fieldname).field.rel.to
159 self.selflink_model = self.model
161 url = self.get_change_url(self.selflink_model, 0)
163 # We don't have an admin for this object, so don't create the
168 # Since we need to add "selflink" to the field list, we need to create
169 # self.fields if it is None.
170 if (self.fields is None):
172 for f in self.model._meta.fields:
173 if f.editable and f.name != "id":
174 self.fields.append(f.name)
176 self.fields = tuple(self.fields) + ("selflink", )
178 if self.readonly_fields is None:
179 self.readonly_fields = ()
181 self.readonly_fields = tuple(self.readonly_fields) + ("selflink", )
183 def selflink(self, obj):
184 if hasattr(self, "selflink_fieldname"):
185 obj = getattr(obj, self.selflink_fieldname)
188 url = self.get_change_url(self.selflink_model, obj.id)
189 return "<a href='%s'>Details</a>" % str(url)
191 return "Not present"
\r
193 selflink.allow_tags = True
194 selflink.short_description = "Details"
196 def has_add_permission(self, request):
197 return not request.user.isReadOnlyUser()
199 def get_readonly_fields(self, request, obj=None):
200 readonly_fields = list(self.readonly_fields)[:]
201 if request.user.isReadOnlyUser():
202 for field in self.fields:
203 if not field in readonly_fields:
204 readonly_fields.append(field)
205 return readonly_fields
207 def formfield_for_dbfield(self, db_field, **kwargs):
208 if (db_field.name == 'backend_status'):
209 kwargs['widget'] = BackendStatusIconWidget()
211 result = super(PlStackTabularInline, self).formfield_for_dbfield(db_field, **kwargs)
213 if (db_field.name == 'backend_status'):
215 result.required = False
219 class PlStackGenericTabularInline(generic.GenericTabularInline):
220 def has_add_permission(self, request):
221 return not request.user.isReadOnlyUser()
223 def get_readonly_fields(self, request, obj=None):
224 readonly_fields = list(self.readonly_fields)[:]
225 if request.user.isReadOnlyUser():
226 for field in self.fields:
227 if not field in readonly_fields:
228 readonly_fields.append(field)
229 return readonly_fields
231 class ReservationInline(PlStackTabularInline):
234 suit_classes = 'suit-tab suit-tab-reservations'
236 def queryset(self, request):
237 return Reservation.select_by_user(request.user)
239 class TagInline(PlStackGenericTabularInline):
242 suit_classes = 'suit-tab suit-tab-tags'
243 fields = ['service', 'name', 'value']
245 def queryset(self, request):
246 return Tag.select_by_user(request.user)
248 class NetworkLookerUpper:
249 """ This is a callable that looks up a network name in a sliver and returns
250 the ip address for that network.
253 byNetworkName = {} # class variable
255 def __init__(self, name):
256 self.short_description = name
258 self.network_name = name
260 def __call__(self, obj):
262 for nbs in obj.networksliver_set.all():
263 if (nbs.network.name == self.network_name):
268 return self.network_name
271 def get(network_name):
272 """ We want to make sure we alwars return the same NetworkLookerUpper
273 because sometimes django will cause them to be instantiated multiple
274 times (and we don't want different ones in form.fields vs
275 SliverInline.readonly_fields).
277 if network_name not in NetworkLookerUpper.byNetworkName:
278 NetworkLookerUpper.byNetworkName[network_name] = NetworkLookerUpper(network_name)
279 return NetworkLookerUpper.byNetworkName[network_name]
281 class SliverInline(PlStackTabularInline):
283 fields = ['backend_status', 'all_ips_string', 'instance_name', 'slice', 'numberCores', 'deploymentNetwork', 'image', 'node']
285 readonly_fields = ['all_ips_string', 'instance_name']
286 suit_classes = 'suit-tab suit-tab-slivers'
288 def queryset(self, request):
289 return Sliver.select_by_user(request.user)
291 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
292 if db_field.name == 'deploymentNetwork':
293 kwargs['queryset'] = Deployment.select_by_acl(request.user)
294 # the inscrutable jquery selector below says:
295 # find the closest parent "tr" to the current element
296 # then find the child with class "field-node"
297 # then find the child with that is a select
299 kwargs['widget'] = forms.Select(attrs={'onChange': "update_nodes(this, $($(this).closest('tr')[0]).find('.field-node select')[0].id)"})
300 #kwargs['widget'] = forms.Select(attrs={'onChange': "console.log($($($(this).closest('tr')[0]).children('.field-node')[0]).children('select')[0].id);"})
302 field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
307 SMBAKER: This is the old code that implemented each network type as a
308 separate column in the sliver table.
310 def _declared_fieldsets(self):
311 # Return None so django will call get_fieldsets and we can insert our
315 def get_readonly_fields(self, request, obj=None):
316 readonly_fields = list(super(SliverInline, self).get_readonly_fields(request, obj))
318 # Lookup the networks that are bound to the slivers, and add those
319 # network names to the list of readonly fields.
321 for sliver in obj.slivers.all():
322 for nbs in sliver.networksliver_set.all():
324 network_name = nbs.network.name
325 if network_name not in [str(x) for x in readonly_fields]:
326 readonly_fields.append(NetworkLookerUpper.get(network_name))
328 return readonly_fields
330 def get_fieldsets(self, request, obj=None):
331 form = self.get_formset(request, obj).form
332 # fields = the read/write files + the read-only fields
333 fields = list(self.fields)
334 for fieldName in self.get_readonly_fields(request,obj):
335 if not fieldName in fields:
336 fields.append(fieldName)
338 return [(None, {'fields': fields})]
341 class SiteInline(PlStackTabularInline):
344 suit_classes = 'suit-tab suit-tab-sites'
346 def queryset(self, request):
347 return Site.select_by_user(request.user)
349 class UserInline(PlStackTabularInline):
351 fields = ['backend_status', 'email', 'firstname', 'lastname']
353 suit_classes = 'suit-tab suit-tab-users'
355 def queryset(self, request):
356 return User.select_by_user(request.user)
358 class SliceInline(PlStackTabularInline):
360 fields = ['backend_status', 'name', 'site', 'serviceClass', 'service']
362 suit_classes = 'suit-tab suit-tab-slices'
364 def queryset(self, request):
365 return Slice.select_by_user(request.user)
367 class NodeInline(PlStackTabularInline):
370 suit_classes = 'suit-tab suit-tab-nodes'
371 fields = ['backend_status', 'name','deployment','site']
373 class DeploymentPrivilegeInline(PlStackTabularInline):
374 model = DeploymentPrivilege
376 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
377 fields = ['backend_status', 'user','role','deployment']
379 def queryset(self, request):
380 return DeploymentPrivilege.select_by_user(request.user)
382 class SitePrivilegeInline(PlStackTabularInline):
383 model = SitePrivilege
385 suit_classes = 'suit-tab suit-tab-siteprivileges'
386 fields = ['backend_status', 'user','site', 'role']
388 def formfield_for_foreignkey(self, db_field, request, **kwargs):
389 if db_field.name == 'site':
390 kwargs['queryset'] = Site.select_by_user(request.user)
392 if db_field.name == 'user':
393 kwargs['queryset'] = User.select_by_user(request.user)
394 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
396 def queryset(self, request):
397 return SitePrivilege.select_by_user(request.user)
399 class SiteDeploymentInline(PlStackTabularInline):
400 model = SiteDeployments
402 suit_classes = 'suit-tab suit-tab-deployments'
403 fields = ['backend_status', 'deployment','site']
405 def formfield_for_foreignkey(self, db_field, request, **kwargs):
406 if db_field.name == 'site':
407 kwargs['queryset'] = Site.select_by_user(request.user)
409 if db_field.name == 'deployment':
410 kwargs['queryset'] = Deployment.select_by_user(request.user)
411 return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
413 def queryset(self, request):
414 return SiteDeployments.select_by_user(request.user)
417 class SlicePrivilegeInline(PlStackTabularInline):
418 model = SlicePrivilege
419 suit_classes = 'suit-tab suit-tab-sliceprivileges'
421 fields = ('backend_status', 'user', 'slice', 'role')
423 def formfield_for_foreignkey(self, db_field, request, **kwargs):
424 if db_field.name == 'slice':
425 kwargs['queryset'] = Slice.select_by_user(request.user)
426 if db_field.name == 'user':
427 kwargs['queryset'] = User.select_by_user(request.user)
429 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
431 def queryset(self, request):
432 return SlicePrivilege.select_by_user(request.user)
434 class SliceNetworkInline(PlStackTabularInline):
435 model = Network.slices.through
436 selflink_fieldname = "network"
438 verbose_name = "Network Connection"
439 verbose_name_plural = "Network Connections"
440 suit_classes = 'suit-tab suit-tab-slicenetworks'
441 fields = ['backend_status', 'network']
443 class ImageDeploymentsInline(PlStackTabularInline):
444 model = ImageDeployments
446 verbose_name = "Image Deployments"
447 verbose_name_plural = "Image Deployments"
448 suit_classes = 'suit-tab suit-tab-imagedeployments'
449 fields = ['backend_status', 'image', 'deployment', 'glance_image_id']
450 readonly_fields = ['glance_image_id']
452 class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
455 def save_model(self, request, obj, form, change):
456 obj.caller = request.user
457 # update openstack connection to use this site/tenant
458 obj.save_by_user(request.user)
460 def delete_model(self, request, obj):
461 obj.delete_by_user(request.user)
463 def save_formset(self, request, form, formset, change):
464 instances = formset.save(commit=False)
465 for instance in instances:
466 instance.save_by_user(request.user)
469 class SliceRoleAdmin(PlanetStackBaseAdmin):
473 class SiteRoleAdmin(PlanetStackBaseAdmin):
477 class DeploymentAdminForm(forms.ModelForm):
478 sites = forms.ModelMultipleChoiceField(
479 queryset=Site.objects.all(),
481 help_text="Select which sites are allowed to host nodes in this deployment",
482 widget=FilteredSelectMultiple(
483 verbose_name=('Sites'), is_stacked=False
486 images = forms.ModelMultipleChoiceField(
487 queryset=Image.objects.all(),
489 help_text="Select which images should be deployed on this deployment",
490 widget=FilteredSelectMultiple(
491 verbose_name=('Images'), is_stacked=False
497 def __init__(self, *args, **kwargs):
498 request = kwargs.pop('request', None)
499 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
501 self.fields['accessControl'].initial = "allow site " + request.user.site.name
503 if self.instance and self.instance.pk:
504 self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments_set.all()]
505 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments_set.all()]
507 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
508 """ helper function for handling m2m relations from the MultipleChoiceField
510 this_obj: the source object we want to link from
512 selected_objs: a list of destination objects we want to link to
514 all_relations: the full set of relations involving this_obj, including ones we don't want
516 relation_class: the class that implements the relation from source to dest
518 local_attrname: field name representing this_obj in relation_class
520 foreign_attrname: field name representing selected_objs in relation_class
522 This function will remove all newobjclass relations from this_obj
523 that are not contained in selected_objs, and add any relations that
524 are in selected_objs but don't exist in the data model yet.
527 existing_dest_objs = []
528 for relation in list(all_relations):
529 if getattr(relation, foreign_attrname) not in selected_objs:
530 #print "deleting site", sdp.site
533 existing_dest_objs.append(getattr(relation, foreign_attrname))
535 for dest_obj in selected_objs:
536 if dest_obj not in existing_dest_objs:
537 #print "adding site", site
538 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
539 relation = relation_class(**kwargs)
542 def save(self, commit=True):
543 deployment = super(DeploymentAdminForm, self).save(commit=False)
549 # save_m2m() doesn't seem to work with 'through' relations. So we
550 # create/destroy the through models ourselves. There has to be
553 self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments_set.all(), SiteDeployments, "deployment", "site")
554 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments_set.all(), ImageDeployments, "deployment", "image")
560 class DeploymentAdminROForm(DeploymentAdminForm):
561 def save(self, commit=True):
562 raise PermissionDenied
564 class SiteAssocInline(PlStackTabularInline):
565 model = Site.deployments.through
567 suit_classes = 'suit-tab suit-tab-sites'
569 class DeploymentAdmin(PlanetStackBaseAdmin):
571 fieldList = ['backend_status', 'name', 'sites', 'images', 'accessControl']
572 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
573 inlines = [DeploymentPrivilegeInline,NodeInline,TagInline] # ,ImageDeploymentsInline]
574 list_display = ['backend_status_icon', 'name']
575 list_display_links = ('backend_status_icon', 'name', )
577 user_readonly_fields = ['name']
579 suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags')) # ,('imagedeployments','Images'))
581 def get_form(self, request, obj=None, **kwargs):
582 if request.user.isReadOnlyUser():
583 kwargs["form"] = DeploymentAdminROForm
585 kwargs["form"] = DeploymentAdminForm
586 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
588 # from stackexchange: pass the request object into the form
590 class AdminFormMetaClass(adminForm):
591 def __new__(cls, *args, **kwargs):
592 kwargs['request'] = request
593 return adminForm(*args, **kwargs)
595 return AdminFormMetaClass
597 class ServiceAttrAsTabInline(PlStackTabularInline):
598 model = ServiceAttribute
599 fields = ['name','value']
601 suit_classes = 'suit-tab suit-tab-serviceattrs'
603 class ServiceAdmin(PlanetStackBaseAdmin):
604 list_display = ("backend_status_icon","name","description","versionNumber","enabled","published")
605 list_display_links = ('backend_status_icon', 'name', )
606 fieldList = ["backend_status","name","description","versionNumber","enabled","published"]
607 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
608 inlines = [ServiceAttrAsTabInline,SliceInline]
610 user_readonly_fields = fieldList
612 suit_form_tabs =(('general', 'Service Details'),
614 ('serviceattrs','Additional Attributes'),
617 class SiteAdmin(PlanetStackBaseAdmin):
618 fieldList = ['backend_status', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
620 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
621 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
623 suit_form_tabs =(('general', 'Site Details'),
625 ('siteprivileges','Privileges'),
626 ('deployments','Deployments'),
631 readonly_fields = ['accountLink']
633 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
635 list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
636 list_display_links = ('backend_status_icon', 'name', )
637 filter_horizontal = ('deployments',)
638 inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentInline]
639 search_fields = ['name']
641 def queryset(self, request):
642 return Site.select_by_user(request.user)
644 def get_formsets(self, request, obj=None):
645 for inline in self.get_inline_instances(request, obj):
646 # hide MyInline in the add view
649 if isinstance(inline, SliceInline):
650 inline.model.caller = request.user
651 yield inline.get_formset(request, obj)
653 def get_formsets(self, request, obj=None):
654 for inline in self.get_inline_instances(request, obj):
655 # hide MyInline in the add view
658 if isinstance(inline, SliverInline):
659 inline.model.caller = request.user
660 yield inline.get_formset(request, obj)
662 def accountLink(self, obj):
663 link_obj = obj.accounts.all()
665 reverse_path = "admin:core_account_change"
666 url = reverse(reverse_path, args =(link_obj[0].id,))
667 return "<a href='%s'>%s</a>" % (url, "view billing details")
669 return "no billing data for this site"
670 accountLink.allow_tags = True
671 accountLink.short_description = "Billing"
673 def save_model(self, request, obj, form, change):
674 # update openstack connection to use this site/tenant
675 obj.save_by_user(request.user)
677 def delete_model(self, request, obj):
678 obj.delete_by_user(request.user)
681 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
682 fieldList = ['backend_status', 'user', 'site', 'role']
684 (None, {'fields': fieldList, 'classes':['collapse']})
686 list_display = ('backend_status_icon', 'user', 'site', 'role')
687 list_display_links = list_display
688 user_readonly_fields = fieldList
689 user_readonly_inlines = []
691 def formfield_for_foreignkey(self, db_field, request, **kwargs):
692 if db_field.name == 'site':
693 if not request.user.is_admin:
694 # only show sites where user is an admin or pi
696 for site_privilege in SitePrivilege.objects.filer(user=request.user):
697 if site_privilege.role.role_type in ['admin', 'pi']:
698 sites.add(site_privilege.site)
699 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
701 if db_field.name == 'user':
702 if not request.user.is_admin:
703 # only show users from sites where caller has admin or pi role
704 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
705 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
706 sites = [site_privilege.site for site_privilege in site_privileges]
707 site_privileges = SitePrivilege.objects.filter(site__in=sites)
708 emails = [site_privilege.user.email for site_privilege in site_privileges]
709 users = User.objects.filter(email__in=emails)
710 kwargs['queryset'] = users
712 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
714 def queryset(self, request):
715 # admins can see all privileges. Users can only see privileges at sites
716 # where they have the admin role or pi role.
717 qs = super(SitePrivilegeAdmin, self).queryset(request)
718 #if not request.user.is_admin:
719 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
720 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
721 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
722 # sites = Site.objects.filter(login_base__in=login_bases)
723 # qs = qs.filter(site__in=sites)
726 class SliceForm(forms.ModelForm):
730 'service': LinkedSelect
733 class SliceAdmin(PlanetStackBaseAdmin):
735 fieldList = ['backend_status', 'name', 'site', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
736 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
737 list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_slivers')
738 list_display_links = ('backend_status_icon', 'name', )
739 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
741 user_readonly_fields = fieldList
743 suit_form_tabs =(('general', 'Slice Details'),
744 ('slicenetworks','Networks'),
745 ('sliceprivileges','Privileges'),
746 ('slivers','Slivers'),
748 ('reservations','Reservations'),
751 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
752 #deployment_nodes = {}
753 #for node in Node.objects.all():
754 # deployment_nodes[node.deployment.id] = get(deployment_nodes, node.deployment.id, []).append( (node.id, node.name) )
756 deployment_nodes = []
757 for node in Node.objects.all():
758 deployment_nodes.append( (node.deployment.id, node.id, node.name) )
760 context["deployment_nodes"] = deployment_nodes
762 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
764 def formfield_for_foreignkey(self, db_field, request, **kwargs):
765 if db_field.name == 'site':
766 kwargs['queryset'] = Site.select_by_user(request.user)
768 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
770 def queryset(self, request):
771 # admins can see all keys. Users can only see slices they belong to.
772 return Slice.select_by_user(request.user)
774 def get_formsets(self, request, obj=None):
775 for inline in self.get_inline_instances(request, obj):
776 # hide MyInline in the add view
779 if isinstance(inline, SliverInline):
780 inline.model.caller = request.user
781 yield inline.get_formset(request, obj)
784 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
786 (None, {'fields': ['backend_status', 'user', 'slice', 'role']})
788 list_display = ('backend_status_icon', 'user', 'slice', 'role')
789 list_display_links = list_display
791 user_readonly_fields = ['user', 'slice', 'role']
792 user_readonly_inlines = []
794 def formfield_for_foreignkey(self, db_field, request, **kwargs):
795 if db_field.name == 'slice':
796 kwargs['queryset'] = Slice.select_by_user(request.user)
798 if db_field.name == 'user':
799 kwargs['queryset'] = User.select_by_user(request.user)
801 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
803 def queryset(self, request):
804 # admins can see all memberships. Users can only see memberships of
805 # slices where they have the admin role.
806 return SlicePrivilege.select_by_user(request.user)
808 def save_model(self, request, obj, form, change):
809 # update openstack connection to use this site/tenant
810 auth = request.session.get('auth', {})
811 auth['tenant'] = obj.slice.slicename
812 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
815 def delete_model(self, request, obj):
816 # update openstack connection to use this site/tenant
817 auth = request.session.get('auth', {})
818 auth['tenant'] = obj.slice.slicename
819 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
823 class ImageAdmin(PlanetStackBaseAdmin):
825 fieldsets = [('Image Details',
826 {'fields': ['backend_status', 'name', 'disk_format', 'container_format'],
827 'classes': ['suit-tab suit-tab-general']})
830 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
832 inlines = [SliverInline, ImageDeploymentsInline]
834 user_readonly_fields = ['name', 'disk_format', 'container_format']
836 list_display = ['backend_status_icon', 'name']
837 list_display_links = ('backend_status_icon', 'name', )
839 class NodeForm(forms.ModelForm):
842 'site': LinkedSelect,
843 'deployment': LinkedSelect
846 class NodeAdmin(PlanetStackBaseAdmin):
848 list_display = ('backend_status_icon', 'name', 'site', 'deployment')
849 list_display_links = ('backend_status_icon', 'name', )
850 list_filter = ('deployment',)
852 inlines = [TagInline,SliverInline]
853 fieldsets = [('Node Details', {'fields': ['backend_status', 'name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
855 user_readonly_fields = ['name','site','deployment']
856 user_readonly_inlines = [TagInline,SliverInline]
858 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
861 class SliverForm(forms.ModelForm):
864 ip = forms.CharField(widget=PlainTextWidget)
865 instance_name = forms.CharField(widget=PlainTextWidget)
867 'ip': PlainTextWidget(),
868 'instance_name': PlainTextWidget(),
869 'slice': LinkedSelect,
870 'deploymentNetwork': LinkedSelect,
871 'node': LinkedSelect,
872 'image': LinkedSelect
875 class TagAdmin(PlanetStackBaseAdmin):
876 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
877 list_display_links = list_display
878 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
879 user_readonly_inlines = []
881 class SliverAdmin(PlanetStackBaseAdmin):
884 ('Sliver Details', {'fields': ['backend_status', 'slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
886 list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']
887 list_display_links = ('backend_status_icon', 'ip',)
889 suit_form_tabs =(('general', 'Sliver Details'),
893 inlines = [TagInline]
895 user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image']
897 def formfield_for_foreignkey(self, db_field, request, **kwargs):
898 if db_field.name == 'slice':
899 kwargs['queryset'] = Slice.select_by_user(request.user)
901 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
903 def queryset(self, request):
904 # admins can see all slivers. Users can only see slivers of
905 # the slices they belong to.
906 return Sliver.select_by_user(request.user)
909 def get_formsets(self, request, obj=None):
910 # make some fields read only if we are updating an existing record
912 #self.readonly_fields = ('ip', 'instance_name')
913 self.readonly_fields = ()
915 self.readonly_fields = ()
916 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
918 for inline in self.get_inline_instances(request, obj):
919 # hide MyInline in the add view
922 if isinstance(inline, SliverInline):
923 inline.model.caller = request.user
924 yield inline.get_formset(request, obj)
926 #def save_model(self, request, obj, form, change):
927 # # update openstack connection to use this site/tenant
928 # auth = request.session.get('auth', {})
929 # auth['tenant'] = obj.slice.name
930 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
931 # obj.creator = request.user
934 #def delete_model(self, request, obj):
935 # # update openstack connection to use this site/tenant
936 # auth = request.session.get('auth', {})
937 # auth['tenant'] = obj.slice.name
938 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
941 class UserCreationForm(forms.ModelForm):
942 """A form for creating new users. Includes all the required
943 fields, plus a repeated password."""
944 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
945 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
949 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
951 def clean_password2(self):
952 # Check that the two password entries match
953 password1 = self.cleaned_data.get("password1")
954 password2 = self.cleaned_data.get("password2")
955 if password1 and password2 and password1 != password2:
956 raise forms.ValidationError("Passwords don't match")
959 def save(self, commit=True):
960 # Save the provided password in hashed format
961 user = super(UserCreationForm, self).save(commit=False)
962 user.password = self.cleaned_data["password1"]
963 #user.set_password(self.cleaned_data["password1"])
969 class UserChangeForm(forms.ModelForm):
970 """A form for updating users. Includes all the fields on
971 the user, but replaces the password field with admin's
972 password hash display field.
974 password = ReadOnlyPasswordHashField(label='Password',
975 help_text= '<a href=\"password/\">Change Password</a>.')
980 def clean_password(self):
981 # Regardless of what the user provides, return the initial value.
982 # This is done here, rather than on the field, because the
983 # field does not have access to the initial value
984 return self.initial["password"]
986 class UserDashboardViewInline(PlStackTabularInline):
987 model = UserDashboardView
989 suit_classes = 'suit-tab suit-tab-dashboards'
990 fields = ['user', 'dashboardView', 'order']
992 class UserAdmin(UserAdmin):
996 # The forms to add and change user instances
997 form = UserChangeForm
998 add_form = UserCreationForm
1000 # The fields to be used in displaying the User model.
1001 # These override the definitions on the base UserAdmin
1002 # that reference specific fields on auth.User.
1003 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
1004 list_filter = ('site',)
1005 inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
1007 fieldListLoginDetails = ['email','site','password','is_active','is_readonly','is_admin','public_key']
1008 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1011 ('Login Details', {'fields': ['backend_status', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1012 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1013 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1014 #('Important dates', {'fields': ('last_login',)}),
1018 'classes': ('wide',),
1019 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
1022 search_fields = ('email',)
1023 ordering = ('email',)
1024 filter_horizontal = ()
1026 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1028 suit_form_tabs =(('general','Login Details'),
1029 ('contact','Contact Information'),
1030 ('sliceprivileges','Slice Privileges'),
1031 ('siteprivileges','Site Privileges'),
1032 ('deploymentprivileges','Deployment Privileges'),
1033 ('dashboards','Dashboard Views'))
1035 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1036 if db_field.name == 'site':
1037 kwargs['queryset'] = Site.select_by_user(request.user)
1039 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1041 def has_add_permission(self, request, obj=None):
1042 return (not self.__user_is_readonly(request))
1044 def has_delete_permission(self, request, obj=None):
1045 return (not self.__user_is_readonly(request))
1047 def get_actions(self,request):
1048 actions = super(UserAdmin,self).get_actions(request)
1050 if self.__user_is_readonly(request):
1051 if 'delete_selected' in actions:
1052 del actions['delete_selected']
1056 def change_view(self,request,object_id, extra_context=None):
1058 if self.__user_is_readonly(request):
1059 if not hasattr(self, "readonly_save"):
1060 # save the original readonly fields
\r
1061 self.readonly_save = self.readonly_fields
\r
1062 self.inlines_save = self.inlines
1063 if hasattr(self, "user_readonly_fields"):
1064 self.readonly_fields=self.user_readonly_fields
1065 if hasattr(self, "user_readonly_inlines"):
1066 self.inlines = self.user_readonly_inlines
1068 if hasattr(self, "readonly_save"):
\r
1069 # restore the original readonly fields
\r
1070 self.readonly_fields = self.readonly_save
\r
1071 self.inlines = self.inlines_save
1074 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1075 except PermissionDenied:
1077 if request.method == 'POST':
1078 raise PermissionDenied
1079 request.readonly = True
1080 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1082 def __user_is_readonly(self, request):
1083 #groups = [x.name for x in request.user.groups.all() ]
1084 #return "readonly" in groups
1085 return request.user.isReadOnlyUser()
1087 def queryset(self, request):
1088 return User.select_by_user(request.user)
1090 def formfield_for_dbfield(self, db_field, **kwargs):
1091 if (db_field.name == 'backend_status'):
1092 kwargs['widget'] = BackendStatusFullWidget(attrs={"title": "foo"})
1093 result = super(UserAdmin, self).formfield_for_dbfield(db_field, **kwargs)
1095 if (db_field.name == 'backend_status'):
1096 result.required = False
1100 class DashboardViewAdmin(PlanetStackBaseAdmin):
1101 fieldsets = [('Dashboard View Details',
1102 {'fields': ['backend_status', 'name', 'url'],
1103 'classes': ['suit-tab suit-tab-general']})
1106 suit_form_tabs =(('general','Dashboard View Details'),)
1108 class ServiceResourceInline(PlStackTabularInline):
1109 model = ServiceResource
1112 class ServiceClassAdmin(PlanetStackBaseAdmin):
1113 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1114 list_display_links = ('backend_status_icon', 'name', )
1115 inlines = [ServiceResourceInline]
1117 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1118 user_readonly_inlines = []
1120 class ReservedResourceInline(PlStackTabularInline):
1121 model = ReservedResource
1123 suit_classes = 'suit-tab suit-tab-reservedresources'
1125 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1126 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1128 if db_field.name == 'resource':
1129 # restrict resources to those that the slice's service class allows
1130 if request._slice is not None:
1131 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1132 if len(field.queryset) > 0:
1133 field.initial = field.queryset.all()[0]
1135 field.queryset = field.queryset.none()
\r
1136 elif db_field.name == 'sliver':
\r
1137 # restrict slivers to those that belong to the slice
\r
1138 if request._slice is not None:
\r
1139 field.queryset = field.queryset.filter(slice = request._slice)
1141 field.queryset = field.queryset.none()
\r
1145 def queryset(self, request):
1146 return ReservedResource.select_by_user(request.user)
1148 class ReservationChangeForm(forms.ModelForm):
1152 'slice' : LinkedSelect
1155 class ReservationAddForm(forms.ModelForm):
1156 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1157 refresh = forms.CharField(widget=forms.HiddenInput())
1160 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1162 def clean_slice(self):
1163 slice = self.cleaned_data.get("slice")
1164 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1166 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1172 'slice' : LinkedSelect
1176 class ReservationAddRefreshForm(ReservationAddForm):
1177 """ This form is displayed when the Reservation Form receives an update
1178 from the Slice dropdown onChange handler. It doesn't validate the
1179 data and doesn't save the data. This will cause the form to be
1183 """ don't validate anything other than slice """
1184 dont_validate_fields = ("startTime", "duration")
1186 def full_clean(self):
1187 result = super(ReservationAddForm, self).full_clean()
1189 for fieldname in self.dont_validate_fields:
1190 if fieldname in self._errors:
1191 del self._errors[fieldname]
1195 """ don't save anything """
1199 class ReservationAdmin(PlanetStackBaseAdmin):
1200 fieldList = ['backend_status', 'slice', 'startTime', 'duration']
1201 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1202 list_display = ('startTime', 'duration')
1203 form = ReservationAddForm
1205 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1207 inlines = [ReservedResourceInline]
1208 user_readonly_fields = fieldList
1210 def add_view(self, request, form_url='', extra_context=None):
1211 timezone.activate(request.user.timezone)
1212 request._refresh = False
1213 request._slice = None
1214 if request.method == 'POST':
1215 # "refresh" will be set to "1" if the form was submitted due to
1216 # a change in the Slice dropdown.
1217 if request.POST.get("refresh","1") == "1":
1218 request._refresh = True
1219 request.POST["refresh"] = "0"
1221 # Keep track of the slice that was selected, so the
1222 # reservedResource inline can filter items for the slice.
1223 request._slice = request.POST.get("slice",None)
1224 if (request._slice is not None):
1225 request._slice = Slice.objects.get(id=request._slice)
1227 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1230 def changelist_view(self, request, extra_context = None):
1231 timezone.activate(request.user.timezone)
1232 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1234 def get_form(self, request, obj=None, **kwargs):
1237 # For changes, set request._slice to the slice already set in the
1239 request._slice = obj.slice
1240 self.form = ReservationChangeForm
1242 if getattr(request, "_refresh", False):
1243 self.form = ReservationAddRefreshForm
1245 self.form = ReservationAddForm
1246 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1248 def get_readonly_fields(self, request, obj=None):
1249 if (obj is not None):
1250 # Prevent slice from being changed after the reservation has been
1256 def queryset(self, request):
1257 return Reservation.select_by_user(request.user)
1259 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1260 list_display = ("backend_status_icon", "name", )
1261 list_display_links = ('backend_status_icon', 'name', )
1262 user_readonly_fields = ['name']
1263 user_readonly_inlines = []
1265 class RouterAdmin(PlanetStackBaseAdmin):
1266 list_display = ("backend_status_icon", "name", )
1267 list_display_links = ('backend_status_icon', 'name', )
1268 user_readonly_fields = ['name']
1269 user_readonly_inlines = []
1271 class RouterInline(PlStackTabularInline):
1272 model = Router.networks.through
1274 verbose_name_plural = "Routers"
1275 verbose_name = "Router"
1276 suit_classes = 'suit-tab suit-tab-routers'
1278 class NetworkParameterInline(PlStackGenericTabularInline):
1279 model = NetworkParameter
1281 verbose_name_plural = "Parameters"
1282 verbose_name = "Parameter"
1283 suit_classes = 'suit-tab suit-tab-netparams'
1284 fields = ['backend_status', 'parameter', 'value']
1286 class NetworkSliversInline(PlStackTabularInline):
1287 fields = ['backend_status', 'network','sliver','ip']
1288 readonly_fields = ("ip", )
1289 model = NetworkSliver
1290 selflink_fieldname = "sliver"
1292 verbose_name_plural = "Slivers"
1293 verbose_name = "Sliver"
1294 suit_classes = 'suit-tab suit-tab-networkslivers'
1296 class NetworkSlicesInline(PlStackTabularInline):
1297 model = NetworkSlice
1298 selflink_fieldname = "slice"
1300 verbose_name_plural = "Slices"
1301 verbose_name = "Slice"
1302 suit_classes = 'suit-tab suit-tab-networkslices'
1303 fields = ['backend_status', 'network','slice']
1305 class NetworkAdmin(PlanetStackBaseAdmin):
1306 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1307 list_display_links = ('backend_status_icon', 'name', )
1308 readonly_fields = ("subnet", )
1310 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1313 (None, {'fields': ['backend_status', 'name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet'], 'classes':['suit-tab suit-tab-general']}),]
1315 user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1318 ('general','Network Details'),
1319 ('netparams', 'Parameters'),
1320 ('networkslivers','Slivers'),
1321 ('networkslices','Slices'),
1322 ('routers','Routers'),
1324 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1325 list_display = ("backend_status_icon", "name", "guaranteedBandwidth", "visibility")
1326 list_display_links = ('backend_status_icon', 'name', )
1327 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1328 user_readonly_inlines = []
1330 # register a signal that caches the user's credentials when they log in
1331 def cache_credentials(sender, user, request, **kwds):
1332 auth = {'username': request.POST['username'],
1333 'password': request.POST['password']}
1334 request.session['auth'] = auth
1335 user_logged_in.connect(cache_credentials)
1337 def dollar_field(fieldName, short_description):
1338 def newFunc(self, obj):
1340 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1342 x=getattr(obj, fieldName, 0.0)
1344 newFunc.short_description = short_description
1347 def right_dollar_field(fieldName, short_description):
1348 def newFunc(self, obj):
1350 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1351 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1353 x=getattr(obj, fieldName, 0.0)
1355 newFunc.short_description = short_description
1356 newFunc.allow_tags = True
1359 class InvoiceChargeInline(PlStackTabularInline):
1362 verbose_name_plural = "Charges"
1363 verbose_name = "Charge"
1364 exclude = ['account']
1365 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1366 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1370 dollar_amount = right_dollar_field("amount", "Amount")
1372 class InvoiceAdmin(admin.ModelAdmin):
1373 list_display = ("date", "account")
1375 inlines = [InvoiceChargeInline]
1377 fields = ["date", "account", "dollar_amount"]
1378 readonly_fields = ["date", "account", "dollar_amount"]
1380 dollar_amount = dollar_field("amount", "Amount")
1382 class InvoiceInline(PlStackTabularInline):
1385 verbose_name_plural = "Invoices"
1386 verbose_name = "Invoice"
1387 fields = ["date", "dollar_amount"]
1388 readonly_fields = ["date", "dollar_amount"]
1389 suit_classes = 'suit-tab suit-tab-accountinvoice'
1393 dollar_amount = right_dollar_field("amount", "Amount")
1395 class PendingChargeInline(PlStackTabularInline):
1398 verbose_name_plural = "Charges"
1399 verbose_name = "Charge"
1400 exclude = ["invoice"]
1401 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1402 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1403 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1407 def queryset(self, request):
1408 qs = super(PendingChargeInline, self).queryset(request)
1409 qs = qs.filter(state="pending")
1412 dollar_amount = right_dollar_field("amount", "Amount")
1414 class PaymentInline(PlStackTabularInline):
1417 verbose_name_plural = "Payments"
1418 verbose_name = "Payment"
1419 fields = ["date", "dollar_amount"]
1420 readonly_fields = ["date", "dollar_amount"]
1421 suit_classes = 'suit-tab suit-tab-accountpayments'
1425 dollar_amount = right_dollar_field("amount", "Amount")
1427 class AccountAdmin(admin.ModelAdmin):
1428 list_display = ("site", "balance_due")
1430 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1433 (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1435 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1438 ('general','Account Details'),
1439 ('accountinvoice', 'Invoices'),
1440 ('accountpayments', 'Payments'),
1441 ('accountpendingcharges','Pending Charges'),
1444 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1445 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1446 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1449 # Now register the new UserAdmin...
1450 admin.site.register(User, UserAdmin)
1451 # ... and, since we're not using Django's builtin permissions,
1452 # unregister the Group model from admin.
1453 #admin.site.unregister(Group)
1455 #Do not show django evolution in the admin interface
1456 from django_evolution.models import Version, Evolution
1457 #admin.site.unregister(Version)
1458 #admin.site.unregister(Evolution)
1461 # When debugging it is often easier to see all the classes, but for regular use
1462 # only the top-levels should be displayed
1465 admin.site.register(Deployment, DeploymentAdmin)
1466 admin.site.register(Site, SiteAdmin)
1467 admin.site.register(Slice, SliceAdmin)
1468 admin.site.register(Service, ServiceAdmin)
1469 admin.site.register(Reservation, ReservationAdmin)
1470 admin.site.register(Network, NetworkAdmin)
1471 admin.site.register(Router, RouterAdmin)
1472 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1473 admin.site.register(Account, AccountAdmin)
1474 admin.site.register(Invoice, InvoiceAdmin)
1477 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1478 admin.site.register(ServiceClass, ServiceClassAdmin)
1479 #admin.site.register(PlanetStack)
1480 admin.site.register(Tag, TagAdmin)
1481 admin.site.register(DeploymentRole)
1482 admin.site.register(SiteRole)
1483 admin.site.register(SliceRole)
1484 admin.site.register(PlanetStackRole)
1485 admin.site.register(Node, NodeAdmin)
1486 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1487 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1488 admin.site.register(Sliver, SliverAdmin)
1489 admin.site.register(Image, ImageAdmin)
1490 admin.site.register(DashboardView, DashboardViewAdmin)