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', 'deploymentNetwork', 'image', 'node']
222 suit_classes = 'suit-tab suit-tab-slivers'
224 class SliverInline(PlStackTabularInline):
226 fields = ['ip', 'instance_name', 'slice', 'numberCores', 'deploymentNetwork', 'image', 'node']
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 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
235 if db_field.name == 'deploymentNetwork':
236 kwargs['queryset'] = Deployment.select_by_acl(request.user)
237 # the inscrutable jquery selector below says:
238 # find the closest parent "tr" to the current element
239 # then find the child with class "field-node"
240 # then find the child with that is a select
242 kwargs['widget'] = forms.Select(attrs={'onChange': "update_nodes(this, $($(this).closest('tr')[0]).find('.field-node select')[0].id)"})
243 #kwargs['widget'] = forms.Select(attrs={'onChange': "console.log($($($(this).closest('tr')[0]).children('.field-node')[0]).children('select')[0].id);"})
245 field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
249 # Note this is breaking in the admin.py when trying to use an inline to add a node/image
250 # def _declared_fieldsets(self):
251 # # Return None so django will call get_fieldsets and we can insert our
255 # def get_readonly_fields(self, request, obj=None):
256 # readonly_fields = super(SliverInline, self).get_readonly_fields(request, obj)
258 # # Lookup the networks that are bound to the slivers, and add those
259 # # network names to the list of readonly fields.
261 # for sliver in obj.slivers.all():
262 # for nbs in sliver.networksliver_set.all():
264 # network_name = nbs.network.name
265 # if network_name not in [str(x) for x in readonly_fields]:
266 # readonly_fields.append(NetworkLookerUpper(network_name))
268 # return readonly_fields
270 # def get_fieldsets(self, request, obj=None):
271 # form = self.get_formset(request, obj).form
272 # # fields = the read/write files + the read-only fields
273 # fields = self.fields
274 # for fieldName in self.get_readonly_fields(request,obj):
275 # if not fieldName in fields:
276 # fields.append(fieldName)
278 # return [(None, {'fields': fields})]
282 class SiteROInline(ReadOnlyTabularInline):
285 fields = ['name', 'login_base', 'site_url', 'enabled']
286 suit_classes = 'suit-tab suit-tab-sites'
288 class SiteInline(PlStackTabularInline):
291 suit_classes = 'suit-tab suit-tab-sites'
293 def queryset(self, request):
294 return Site.select_by_user(request.user)
296 class UserROInline(ReadOnlyTabularInline):
298 fields = ['email', 'firstname', 'lastname']
300 suit_classes = 'suit-tab suit-tab-users'
302 class UserInline(PlStackTabularInline):
304 fields = ['email', 'firstname', 'lastname']
306 suit_classes = 'suit-tab suit-tab-users'
308 def queryset(self, request):
309 return User.select_by_user(request.user)
311 class SliceROInline(ReadOnlyTabularInline):
313 suit_classes = 'suit-tab suit-tab-slices'
314 fields = ['name','site', 'serviceClass', 'service']
316 class SliceInline(PlStackTabularInline):
318 fields = ['name','site', 'serviceClass', 'service']
320 suit_classes = 'suit-tab suit-tab-slices'
322 def queryset(self, request):
323 return Slice.select_by_user(request.user)
325 class NodeROInline(ReadOnlyTabularInline):
328 suit_classes = 'suit-tab suit-tab-nodes'
329 fields = ['name','deployment','site']
331 class NodeInline(PlStackTabularInline):
334 suit_classes = 'suit-tab suit-tab-nodes'
335 fields = ['name','deployment','site']
337 class DeploymentPrivilegeROInline(ReadOnlyTabularInline):
338 model = DeploymentPrivilege
340 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
341 fields = ['user','role','deployment']
343 class DeploymentPrivilegeInline(PlStackTabularInline):
344 model = DeploymentPrivilege
346 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
347 fields = ['user','role','deployment']
349 def queryset(self, request):
350 return DeploymentPrivilege.select_by_user(request.user)
352 #CLEANUP DOUBLE SitePrivilegeInline
353 class SitePrivilegeROInline(ReadOnlyTabularInline):
354 model = SitePrivilege
356 suit_classes = 'suit-tab suit-tab-siteprivileges'
357 fields = ['user','site', 'role']
359 class SitePrivilegeInline(PlStackTabularInline):
360 model = SitePrivilege
362 suit_classes = 'suit-tab suit-tab-siteprivileges'
363 fields = ['user','site', 'role']
365 def formfield_for_foreignkey(self, db_field, request, **kwargs):
366 if db_field.name == 'site':
367 kwargs['queryset'] = Site.select_by_user(request.user)
369 if db_field.name == 'user':
370 kwargs['queryset'] = User.select_by_user(request.user)
371 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
373 def queryset(self, request):
374 return SitePrivilege.select_by_user(request.user)
376 class SiteDeploymentROInline(ReadOnlyTabularInline):
377 model = SiteDeployments
378 #model = Site.deployments.through
380 suit_classes = 'suit-tab suit-tab-deployments'
381 fields = ['deployment','site']
383 class SiteDeploymentInline(PlStackTabularInline):
384 model = SiteDeployments
385 #model = Site.deployments.through
387 suit_classes = 'suit-tab suit-tab-deployments'
388 fields = ['deployment','site']
390 def formfield_for_foreignkey(self, db_field, request, **kwargs):
391 if db_field.name == 'site':
392 kwargs['queryset'] = Site.select_by_user(request.user)
394 if db_field.name == 'deployment':
395 kwargs['queryset'] = Deployment.select_by_user(request.user)
396 return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
398 def queryset(self, request):
399 return SiteDeployments.select_by_user(request.user)
402 class SlicePrivilegeROInline(ReadOnlyTabularInline):
403 model = SlicePrivilege
405 suit_classes = 'suit-tab suit-tab-sliceprivileges'
406 fields = ['user', 'slice', 'role']
408 class SlicePrivilegeInline(PlStackTabularInline):
409 model = SlicePrivilege
410 suit_classes = 'suit-tab suit-tab-sliceprivileges'
412 fields = ('user', 'slice','role')
414 def formfield_for_foreignkey(self, db_field, request, **kwargs):
415 if db_field.name == 'slice':
416 kwargs['queryset'] = Slice.select_by_user(request.user)
417 if db_field.name == 'user':
418 kwargs['queryset'] = User.select_by_user(request.user)
420 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
422 def queryset(self, request):
423 return SlicePrivilege.select_by_user(request.user)
425 class SliceNetworkROInline(ReadOnlyTabularInline):
426 model = Network.slices.through
428 verbose_name = "Network Connection"
429 verbose_name_plural = "Network Connections"
430 suit_classes = 'suit-tab suit-tab-slicenetworks'
433 class SliceNetworkInline(PlStackTabularInline):
434 model = Network.slices.through
435 selflink_fieldname = "network"
437 verbose_name = "Network Connection"
438 verbose_name_plural = "Network Connections"
439 suit_classes = 'suit-tab suit-tab-slicenetworks'
442 class ImageDeploymentsROInline(ReadOnlyTabularInline):
443 model = ImageDeployments
445 verbose_name = "Image Deployments"
446 verbose_name_plural = "Image Deployments"
447 suit_classes = 'suit-tab suit-tab-imagedeployments'
448 fields = ['image', 'deployment', 'glance_image_id']
450 class ImageDeploymentsInline(PlStackTabularInline):
451 model = ImageDeployments
453 verbose_name = "Image Deployments"
454 verbose_name_plural = "Image Deployments"
455 suit_classes = 'suit-tab suit-tab-imagedeployments'
456 fields = ['image', 'deployment', 'glance_image_id']
457 readonly_fields = ['glance_image_id']
459 class PlainTextWidget(forms.HiddenInput):
460 input_type = 'hidden'
462 def render(self, name, value, attrs=None):
465 return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
467 class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
470 def save_model(self, request, obj, form, change):
471 obj.caller = request.user
472 # update openstack connection to use this site/tenant
473 obj.save_by_user(request.user)
475 def delete_model(self, request, obj):
476 obj.delete_by_user(request.user)
478 def save_formset(self, request, form, formset, change):
479 instances = formset.save(commit=False)
480 for instance in instances:
481 instance.save_by_user(request.user)
484 class SliceRoleAdmin(PlanetStackBaseAdmin):
488 class SiteRoleAdmin(PlanetStackBaseAdmin):
492 class DeploymentAdminForm(forms.ModelForm):
493 sites = forms.ModelMultipleChoiceField(
494 queryset=Site.objects.all(),
496 help_text="Select which sites are allowed to host nodes in this deployment",
497 widget=FilteredSelectMultiple(
498 verbose_name=('Sites'), is_stacked=False
501 images = forms.ModelMultipleChoiceField(
502 queryset=Image.objects.all(),
504 help_text="Select which images should be deployed on this deployment",
505 widget=FilteredSelectMultiple(
506 verbose_name=('Images'), is_stacked=False
512 def __init__(self, *args, **kwargs):
513 request = kwargs.pop('request', None)
514 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
516 self.fields['accessControl'].initial = "allow site " + request.user.site.name
518 if self.instance and self.instance.pk:
519 self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments_set.all()]
520 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments_set.all()]
522 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
523 """ helper function for handling m2m relations from the MultipleChoiceField
525 this_obj: the source object we want to link from
527 selected_objs: a list of destination objects we want to link to
529 all_relations: the full set of relations involving this_obj, including ones we don't want
531 relation_class: the class that implements the relation from source to dest
533 local_attrname: field name representing this_obj in relation_class
535 foreign_attrname: field name representing selected_objs in relation_class
537 This function will remove all newobjclass relations from this_obj
538 that are not contained in selected_objs, and add any relations that
539 are in selected_objs but don't exist in the data model yet.
542 existing_dest_objs = []
543 for relation in list(all_relations):
544 if getattr(relation, foreign_attrname) not in selected_objs:
545 #print "deleting site", sdp.site
548 existing_dest_objs.append(getattr(relation, foreign_attrname))
550 for dest_obj in selected_objs:
551 if dest_obj not in existing_dest_objs:
552 #print "adding site", site
553 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
554 relation = relation_class(**kwargs)
557 def save(self, commit=True):
558 deployment = super(DeploymentAdminForm, self).save(commit=False)
564 # save_m2m() doesn't seem to work with 'through' relations. So we
565 # create/destroy the through models ourselves. There has to be
568 self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments_set.all(), SiteDeployments, "deployment", "site")
569 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments_set.all(), ImageDeployments, "deployment", "image")
575 class DeploymentAdminROForm(DeploymentAdminForm):
576 def save(self, commit=True):
577 raise PermissionDenied
579 class SiteAssocInline(PlStackTabularInline):
580 model = Site.deployments.through
582 suit_classes = 'suit-tab suit-tab-sites'
584 class DeploymentAdmin(PlanetStackBaseAdmin):
586 fieldList = ['name','sites', 'images', 'accessControl']
587 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
588 inlines = [DeploymentPrivilegeInline,NodeInline,TagInline] # ,ImageDeploymentsInline]
590 user_readonly_inlines = [DeploymentPrivilegeROInline,NodeROInline,TagROInline] # ,ImageDeploymentsROInline]
591 user_readonly_fields = ['name']
593 suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags')) # ,('imagedeployments','Images'))
595 def get_form(self, request, obj=None, **kwargs):
596 if request.user.isReadOnlyUser():
597 kwargs["form"] = DeploymentAdminROForm
599 kwargs["form"] = DeploymentAdminForm
600 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
602 # from stackexchange: pass the request object into the form
604 class AdminFormMetaClass(adminForm):
605 def __new__(cls, *args, **kwargs):
606 kwargs['request'] = request
607 return adminForm(*args, **kwargs)
609 return AdminFormMetaClass
611 class ServiceAttrAsTabROInline(ReadOnlyTabularInline):
612 model = ServiceAttribute
613 fields = ['name','value']
615 suit_classes = 'suit-tab suit-tab-serviceattrs'
617 class ServiceAttrAsTabInline(PlStackTabularInline):
618 model = ServiceAttribute
619 fields = ['name','value']
621 suit_classes = 'suit-tab suit-tab-serviceattrs'
623 class ServiceAdmin(PlanetStackBaseAdmin):
624 list_display = ("name","description","versionNumber","enabled","published")
625 fieldList = ["name","description","versionNumber","enabled","published"]
626 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
627 inlines = [ServiceAttrAsTabInline,SliceInline]
629 user_readonly_fields = fieldList
630 user_readonly_inlines = [ServiceAttrAsTabROInline,SliceROInline]
632 suit_form_tabs =(('general', 'Service Details'),
634 ('serviceattrs','Additional Attributes'),
637 class SiteAdmin(PlanetStackBaseAdmin):
638 fieldList = ['name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
640 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
641 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
643 suit_form_tabs =(('general', 'Site Details'),
645 ('siteprivileges','Privileges'),
646 ('deployments','Deployments'),
651 readonly_fields = ['accountLink']
653 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
654 user_readonly_inlines = [SliceROInline,UserROInline,TagROInline, NodeROInline, SitePrivilegeROInline,SiteDeploymentROInline]
656 list_display = ('name', 'login_base','site_url', 'enabled')
657 filter_horizontal = ('deployments',)
658 inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentInline]
659 search_fields = ['name']
661 def queryset(self, request):
662 return Site.select_by_user(request.user)
664 def get_formsets(self, request, obj=None):
665 for inline in self.get_inline_instances(request, obj):
666 # hide MyInline in the add view
669 if isinstance(inline, SliceInline):
670 inline.model.caller = request.user
671 yield inline.get_formset(request, obj)
673 def get_formsets(self, request, obj=None):
674 for inline in self.get_inline_instances(request, obj):
675 # hide MyInline in the add view
678 if isinstance(inline, SliverInline):
679 inline.model.caller = request.user
680 yield inline.get_formset(request, obj)
682 def accountLink(self, obj):
683 link_obj = obj.accounts.all()
685 reverse_path = "admin:core_account_change"
686 url = reverse(reverse_path, args =(link_obj[0].id,))
687 return "<a href='%s'>%s</a>" % (url, "view billing details")
689 return "no billing data for this site"
690 accountLink.allow_tags = True
691 accountLink.short_description = "Billing"
693 def save_model(self, request, obj, form, change):
694 # update openstack connection to use this site/tenant
695 obj.save_by_user(request.user)
697 def delete_model(self, request, obj):
698 obj.delete_by_user(request.user)
701 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
702 fieldList = ['user', 'site', 'role']
704 (None, {'fields': fieldList, 'classes':['collapse']})
706 list_display = ('user', 'site', 'role')
707 user_readonly_fields = fieldList
708 user_readonly_inlines = []
710 def formfield_for_foreignkey(self, db_field, request, **kwargs):
711 if db_field.name == 'site':
712 if not request.user.is_admin:
713 # only show sites where user is an admin or pi
715 for site_privilege in SitePrivilege.objects.filer(user=request.user):
716 if site_privilege.role.role_type in ['admin', 'pi']:
717 sites.add(site_privilege.site)
718 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
720 if db_field.name == 'user':
721 if not request.user.is_admin:
722 # only show users from sites where caller has admin or pi role
723 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
724 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
725 sites = [site_privilege.site for site_privilege in site_privileges]
726 site_privileges = SitePrivilege.objects.filter(site__in=sites)
727 emails = [site_privilege.user.email for site_privilege in site_privileges]
728 users = User.objects.filter(email__in=emails)
729 kwargs['queryset'] = users
731 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
733 def queryset(self, request):
734 # admins can see all privileges. Users can only see privileges at sites
735 # where they have the admin role or pi role.
736 qs = super(SitePrivilegeAdmin, self).queryset(request)
737 #if not request.user.is_admin:
738 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
739 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
740 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
741 # sites = Site.objects.filter(login_base__in=login_bases)
742 # qs = qs.filter(site__in=sites)
745 class SliceForm(forms.ModelForm):
749 'service': LinkedSelect
752 class SliceAdmin(PlanetStackBaseAdmin):
754 fieldList = ['name', 'site', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
755 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
756 list_display = ('name', 'site','serviceClass', 'slice_url', 'max_slivers')
757 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
759 user_readonly_fields = fieldList
760 user_readonly_inlines = [SlicePrivilegeROInline,SliverROInline,TagROInline, ReservationROInline, SliceNetworkROInline]
762 suit_form_tabs =(('general', 'Slice Details'),
763 ('slicenetworks','Networks'),
764 ('sliceprivileges','Privileges'),
765 ('slivers','Slivers'),
767 ('reservations','Reservations'),
770 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
771 #deployment_nodes = {}
772 #for node in Node.objects.all():
773 # deployment_nodes[node.deployment.id] = get(deployment_nodes, node.deployment.id, []).append( (node.id, node.name) )
775 deployment_nodes = []
776 for node in Node.objects.all():
777 deployment_nodes.append( (node.deployment.id, node.id, node.name) )
779 context["deployment_nodes"] = deployment_nodes
781 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
783 def formfield_for_foreignkey(self, db_field, request, **kwargs):
784 if db_field.name == 'site':
785 kwargs['queryset'] = Site.select_by_user(request.user)
787 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
789 def queryset(self, request):
790 # admins can see all keys. Users can only see slices they belong to.
791 return Slice.select_by_user(request.user)
793 def get_formsets(self, request, obj=None):
794 for inline in self.get_inline_instances(request, obj):
795 # hide MyInline in the add view
798 if isinstance(inline, SliverInline):
799 inline.model.caller = request.user
800 yield inline.get_formset(request, obj)
803 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
805 (None, {'fields': ['user', 'slice', 'role']})
807 list_display = ('user', 'slice', 'role')
809 user_readonly_fields = ['user', 'slice', 'role']
810 user_readonly_inlines = []
812 def formfield_for_foreignkey(self, db_field, request, **kwargs):
813 if db_field.name == 'slice':
814 kwargs['queryset'] = Slice.select_by_user(request.user)
816 if db_field.name == 'user':
817 kwargs['queryset'] = User.select_by_user(request.user)
819 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
821 def queryset(self, request):
822 # admins can see all memberships. Users can only see memberships of
823 # slices where they have the admin role.
824 return SlicePrivilege.select_by_user(request.user)
826 def save_model(self, request, obj, form, change):
827 # update openstack connection to use this site/tenant
828 auth = request.session.get('auth', {})
829 auth['tenant'] = obj.slice.slicename
830 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
833 def delete_model(self, request, obj):
834 # update openstack connection to use this site/tenant
835 auth = request.session.get('auth', {})
836 auth['tenant'] = obj.slice.slicename
837 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
841 class ImageAdmin(PlanetStackBaseAdmin):
843 fieldsets = [('Image Details',
844 {'fields': ['name', 'disk_format', 'container_format'],
845 'classes': ['suit-tab suit-tab-general']})
848 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
850 inlines = [SliverInline, ImageDeploymentsInline]
852 user_readonly_fields = ['name', 'disk_format', 'container_format']
853 user_readonly_inlines = [SliverROInline, ImageDeploymentsROInline]
855 class NodeForm(forms.ModelForm):
858 'site': LinkedSelect,
859 'deployment': LinkedSelect
862 class NodeAdmin(PlanetStackBaseAdmin):
864 list_display = ('name', 'site', 'deployment')
865 list_filter = ('deployment',)
867 inlines = [TagInline,SliverInline]
868 fieldsets = [('Node Details', {'fields': ['name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
870 user_readonly_fields = ['name','site','deployment']
871 user_readonly_inlines = [TagInline,SliverInline]
873 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
876 class SliverForm(forms.ModelForm):
879 ip = forms.CharField(widget=PlainTextWidget)
880 instance_name = forms.CharField(widget=PlainTextWidget)
882 'ip': PlainTextWidget(),
883 'instance_name': PlainTextWidget(),
884 'slice': LinkedSelect,
885 'deploymentNetwork': LinkedSelect,
886 'node': LinkedSelect,
887 'image': LinkedSelect
890 class TagAdmin(PlanetStackBaseAdmin):
891 list_display = ['service', 'name', 'value', 'content_type', 'content_object',]
892 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
893 user_readonly_inlines = []
895 class SliverAdmin(PlanetStackBaseAdmin):
898 ('Sliver Details', {'fields': ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
900 list_display = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']
902 suit_form_tabs =(('general', 'Sliver Details'),
906 inlines = [TagInline]
908 user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image']
909 user_readonly_inlines = [TagROInline]
911 def formfield_for_foreignkey(self, db_field, request, **kwargs):
912 if db_field.name == 'slice':
913 kwargs['queryset'] = Slice.select_by_user(request.user)
915 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
917 def queryset(self, request):
918 # admins can see all slivers. Users can only see slivers of
919 # the slices they belong to.
920 return Sliver.select_by_user(request.user)
923 def get_formsets(self, request, obj=None):
924 # make some fields read only if we are updating an existing record
926 #self.readonly_fields = ('ip', 'instance_name')
927 self.readonly_fields = ()
929 self.readonly_fields = ()
930 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
932 for inline in self.get_inline_instances(request, obj):
933 # hide MyInline in the add view
936 if isinstance(inline, SliverInline):
937 inline.model.caller = request.user
938 yield inline.get_formset(request, obj)
940 #def save_model(self, request, obj, form, change):
941 # # update openstack connection to use this site/tenant
942 # auth = request.session.get('auth', {})
943 # auth['tenant'] = obj.slice.name
944 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
945 # obj.creator = request.user
948 #def delete_model(self, request, obj):
949 # # update openstack connection to use this site/tenant
950 # auth = request.session.get('auth', {})
951 # auth['tenant'] = obj.slice.name
952 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
955 class UserCreationForm(forms.ModelForm):
956 """A form for creating new users. Includes all the required
957 fields, plus a repeated password."""
958 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
959 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
963 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
965 def clean_password2(self):
966 # Check that the two password entries match
967 password1 = self.cleaned_data.get("password1")
968 password2 = self.cleaned_data.get("password2")
969 if password1 and password2 and password1 != password2:
970 raise forms.ValidationError("Passwords don't match")
973 def save(self, commit=True):
974 # Save the provided password in hashed format
975 user = super(UserCreationForm, self).save(commit=False)
976 user.password = self.cleaned_data["password1"]
977 #user.set_password(self.cleaned_data["password1"])
983 class UserChangeForm(forms.ModelForm):
984 """A form for updating users. Includes all the fields on
985 the user, but replaces the password field with admin's
986 password hash display field.
988 password = ReadOnlyPasswordHashField(label='Password',
989 help_text= '<a href=\"password/\">Change Password</a>.')
994 def clean_password(self):
995 # Regardless of what the user provides, return the initial value.
996 # This is done here, rather than on the field, because the
997 # field does not have access to the initial value
998 return self.initial["password"]
1000 class UserDashboardViewInline(PlStackTabularInline):
1001 model = UserDashboardView
1003 suit_classes = 'suit-tab suit-tab-dashboards'
1004 fields = ['user', 'dashboardView', 'order']
1006 class UserDashboardViewROInline(ReadOnlyTabularInline):
1007 model = UserDashboardView
1009 suit_classes = 'suit-tab suit-tab-dashboards'
1010 fields = ['user', 'dashboardView', 'order']
1012 class UserAdmin(UserAdmin):
1016 # The forms to add and change user instances
1017 form = UserChangeForm
1018 add_form = UserCreationForm
1020 # The fields to be used in displaying the User model.
1021 # These override the definitions on the base UserAdmin
1022 # that reference specific fields on auth.User.
1023 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
1024 list_filter = ('site',)
1025 inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
1027 fieldListLoginDetails = ['email','site','password','is_active','is_readonly','is_admin','public_key']
1028 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1031 ('Login Details', {'fields': ['email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1032 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1033 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1034 #('Important dates', {'fields': ('last_login',)}),
1038 'classes': ('wide',),
1039 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
1042 search_fields = ('email',)
1043 ordering = ('email',)
1044 filter_horizontal = ()
1046 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1047 user_readonly_inlines = [SlicePrivilegeROInline,SitePrivilegeROInline,DeploymentPrivilegeROInline,UserDashboardViewROInline]
1049 suit_form_tabs =(('general','Login Details'),
1050 ('contact','Contact Information'),
1051 ('sliceprivileges','Slice Privileges'),
1052 ('siteprivileges','Site Privileges'),
1053 ('deploymentprivileges','Deployment Privileges'),
1054 ('dashboards','Dashboard Views'))
1056 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1057 if db_field.name == 'site':
1058 kwargs['queryset'] = Site.select_by_user(request.user)
1060 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1062 def has_add_permission(self, request, obj=None):
1063 return (not self.__user_is_readonly(request))
1065 def has_delete_permission(self, request, obj=None):
1066 return (not self.__user_is_readonly(request))
1068 def get_actions(self,request):
1069 actions = super(UserAdmin,self).get_actions(request)
1071 if self.__user_is_readonly(request):
1072 if 'delete_selected' in actions:
1073 del actions['delete_selected']
1077 def change_view(self,request,object_id, extra_context=None):
1079 if self.__user_is_readonly(request):
1080 if not hasattr(self, "readonly_save"):
1081 # save the original readonly fields
\r
1082 self.readonly_save = self.readonly_fields
\r
1083 self.inlines_save = self.inlines
1084 self.readonly_fields=self.user_readonly_fields
1085 self.inlines = self.user_readonly_inlines
1087 if hasattr(self, "readonly_save"):
\r
1088 # restore the original readonly fields
\r
1089 self.readonly_fields = self.readonly_save
\r
1090 self.inlines = self.inlines_save
1093 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1094 except PermissionDenied:
1096 if request.method == 'POST':
1097 raise PermissionDenied
1098 request.readonly = True
1099 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1101 def __user_is_readonly(self, request):
1102 #groups = [x.name for x in request.user.groups.all() ]
1103 #return "readonly" in groups
1104 return request.user.isReadOnlyUser()
1106 def queryset(self, request):
1107 return User.select_by_user(request.user)
1109 class DashboardViewAdmin(PlanetStackBaseAdmin):
1110 fieldsets = [('Dashboard View Details',
1111 {'fields': ['name', 'url'],
1112 'classes': ['suit-tab suit-tab-general']})
1115 suit_form_tabs =(('general','Dashboard View Details'),)
1117 class ServiceResourceROInline(ReadOnlyTabularInline):
1118 model = ServiceResource
1120 fields = ['serviceClass', 'name', 'maxUnitsDeployment', 'maxUnitsNode', 'maxDuration', 'bucketInRate', 'bucketMaxSize', 'cost', 'calendarReservable']
1122 class ServiceResourceInline(PlStackTabularInline):
1123 model = ServiceResource
1126 class ServiceClassAdmin(PlanetStackBaseAdmin):
1127 list_display = ('name', 'commitment', 'membershipFee')
1128 inlines = [ServiceResourceInline]
1130 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1131 user_readonly_inlines = []
1133 class ReservedResourceROInline(ReadOnlyTabularInline):
1134 model = ReservedResource
1136 fields = ['sliver', 'resource','quantity','reservationSet']
1137 suit_classes = 'suit-tab suit-tab-reservedresources'
1139 class ReservedResourceInline(PlStackTabularInline):
1140 model = ReservedResource
1142 suit_classes = 'suit-tab suit-tab-reservedresources'
1144 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1145 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1147 if db_field.name == 'resource':
1148 # restrict resources to those that the slice's service class allows
1149 if request._slice is not None:
1150 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1151 if len(field.queryset) > 0:
1152 field.initial = field.queryset.all()[0]
1154 field.queryset = field.queryset.none()
\r
1155 elif db_field.name == 'sliver':
\r
1156 # restrict slivers to those that belong to the slice
\r
1157 if request._slice is not None:
\r
1158 field.queryset = field.queryset.filter(slice = request._slice)
1160 field.queryset = field.queryset.none()
\r
1164 def queryset(self, request):
1165 return ReservedResource.select_by_user(request.user)
1167 class ReservationChangeForm(forms.ModelForm):
1171 'slice' : LinkedSelect
1174 class ReservationAddForm(forms.ModelForm):
1175 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1176 refresh = forms.CharField(widget=forms.HiddenInput())
1179 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1181 def clean_slice(self):
1182 slice = self.cleaned_data.get("slice")
1183 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1185 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1191 'slice' : LinkedSelect
1195 class ReservationAddRefreshForm(ReservationAddForm):
1196 """ This form is displayed when the Reservation Form receives an update
1197 from the Slice dropdown onChange handler. It doesn't validate the
1198 data and doesn't save the data. This will cause the form to be
1202 """ don't validate anything other than slice """
1203 dont_validate_fields = ("startTime", "duration")
1205 def full_clean(self):
1206 result = super(ReservationAddForm, self).full_clean()
1208 for fieldname in self.dont_validate_fields:
1209 if fieldname in self._errors:
1210 del self._errors[fieldname]
1214 """ don't save anything """
1218 class ReservationAdmin(PlanetStackBaseAdmin):
1219 fieldList = ['slice', 'startTime', 'duration']
1220 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1221 list_display = ('startTime', 'duration')
1222 form = ReservationAddForm
1224 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1226 inlines = [ReservedResourceInline]
1227 user_readonly_inlines = [ReservedResourceROInline]
1228 user_readonly_fields = fieldList
1230 def add_view(self, request, form_url='', extra_context=None):
1231 timezone.activate(request.user.timezone)
1232 request._refresh = False
1233 request._slice = None
1234 if request.method == 'POST':
1235 # "refresh" will be set to "1" if the form was submitted due to
1236 # a change in the Slice dropdown.
1237 if request.POST.get("refresh","1") == "1":
1238 request._refresh = True
1239 request.POST["refresh"] = "0"
1241 # Keep track of the slice that was selected, so the
1242 # reservedResource inline can filter items for the slice.
1243 request._slice = request.POST.get("slice",None)
1244 if (request._slice is not None):
1245 request._slice = Slice.objects.get(id=request._slice)
1247 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1250 def changelist_view(self, request, extra_context = None):
1251 timezone.activate(request.user.timezone)
1252 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1254 def get_form(self, request, obj=None, **kwargs):
1257 # For changes, set request._slice to the slice already set in the
1259 request._slice = obj.slice
1260 self.form = ReservationChangeForm
1262 if getattr(request, "_refresh", False):
1263 self.form = ReservationAddRefreshForm
1265 self.form = ReservationAddForm
1266 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1268 def get_readonly_fields(self, request, obj=None):
1269 if (obj is not None):
1270 # Prevent slice from being changed after the reservation has been
1276 def queryset(self, request):
1277 return Reservation.select_by_user(request.user)
1279 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1280 list_display = ("name", )
1281 user_readonly_fields = ['name']
1282 user_readonly_inlines = []
1284 class RouterAdmin(PlanetStackBaseAdmin):
1285 list_display = ("name", )
1286 user_readonly_fields = ['name']
1287 user_readonly_inlines = []
1289 class RouterROInline(ReadOnlyTabularInline):
1290 model = Router.networks.through
1292 verbose_name_plural = "Routers"
1293 verbose_name = "Router"
1294 suit_classes = 'suit-tab suit-tab-routers'
1296 fields = ['name', 'owner', 'permittedNetworks', 'networks']
1298 class RouterInline(PlStackTabularInline):
1299 model = Router.networks.through
1301 verbose_name_plural = "Routers"
1302 verbose_name = "Router"
1303 suit_classes = 'suit-tab suit-tab-routers'
1305 class NetworkParameterROInline(ReadOnlyTabularInline):
1306 model = NetworkParameter
1308 verbose_name_plural = "Parameters"
1309 verbose_name = "Parameter"
1310 suit_classes = 'suit-tab suit-tab-netparams'
1311 fields = ['parameter', 'value', 'content_type', 'object_id', 'content_object']
1313 class NetworkParameterInline(generic.GenericTabularInline):
1314 model = NetworkParameter
1316 verbose_name_plural = "Parameters"
1317 verbose_name = "Parameter"
1318 suit_classes = 'suit-tab suit-tab-netparams'
1320 class NetworkSliversROInline(ReadOnlyTabularInline):
1321 fields = ['network', 'sliver', 'ip', 'port_id']
1322 model = NetworkSliver
1324 verbose_name_plural = "Slivers"
1325 verbose_name = "Sliver"
1326 suit_classes = 'suit-tab suit-tab-networkslivers'
1328 class NetworkSliversInline(PlStackTabularInline):
1329 readonly_fields = ("ip", )
1330 model = NetworkSliver
1331 selflink_fieldname = "sliver"
1333 verbose_name_plural = "Slivers"
1334 verbose_name = "Sliver"
1335 suit_classes = 'suit-tab suit-tab-networkslivers'
1337 class NetworkSlicesROInline(ReadOnlyTabularInline):
1338 model = NetworkSlice
1340 verbose_name_plural = "Slices"
1341 verbose_name = "Slice"
1342 suit_classes = 'suit-tab suit-tab-networkslices'
1343 fields = ['network','slice']
1345 class NetworkSlicesInline(PlStackTabularInline):
1346 model = NetworkSlice
1347 selflink_fieldname = "slice"
1349 verbose_name_plural = "Slices"
1350 verbose_name = "Slice"
1351 suit_classes = 'suit-tab suit-tab-networkslices'
1353 class NetworkAdmin(PlanetStackBaseAdmin):
1354 list_display = ("name", "subnet", "ports", "labels")
1355 readonly_fields = ("subnet", )
1357 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1360 (None, {'fields': ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet'], 'classes':['suit-tab suit-tab-general']}),]
1362 user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1363 user_readonly_inlines = [NetworkParameterROInline, NetworkSliversROInline, NetworkSlicesROInline, RouterROInline]
1366 ('general','Network Details'),
1367 ('netparams', 'Parameters'),
1368 ('networkslivers','Slivers'),
1369 ('networkslices','Slices'),
1370 ('routers','Routers'),
1372 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1373 list_display = ("name", "guaranteedBandwidth", "visibility")
1374 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1375 user_readonly_inlines = []
1377 # register a signal that caches the user's credentials when they log in
1378 def cache_credentials(sender, user, request, **kwds):
1379 auth = {'username': request.POST['username'],
1380 'password': request.POST['password']}
1381 request.session['auth'] = auth
1382 user_logged_in.connect(cache_credentials)
1384 def dollar_field(fieldName, short_description):
1385 def newFunc(self, obj):
1387 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1389 x=getattr(obj, fieldName, 0.0)
1391 newFunc.short_description = short_description
1394 def right_dollar_field(fieldName, short_description):
1395 def newFunc(self, obj):
1397 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1398 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1400 x=getattr(obj, fieldName, 0.0)
1402 newFunc.short_description = short_description
1403 newFunc.allow_tags = True
1406 class InvoiceChargeInline(PlStackTabularInline):
1409 verbose_name_plural = "Charges"
1410 verbose_name = "Charge"
1411 exclude = ['account']
1412 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1413 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1417 dollar_amount = right_dollar_field("amount", "Amount")
1419 class InvoiceAdmin(admin.ModelAdmin):
1420 list_display = ("date", "account")
1422 inlines = [InvoiceChargeInline]
1424 fields = ["date", "account", "dollar_amount"]
1425 readonly_fields = ["date", "account", "dollar_amount"]
1427 dollar_amount = dollar_field("amount", "Amount")
1429 class InvoiceInline(PlStackTabularInline):
1432 verbose_name_plural = "Invoices"
1433 verbose_name = "Invoice"
1434 fields = ["date", "dollar_amount"]
1435 readonly_fields = ["date", "dollar_amount"]
1436 suit_classes = 'suit-tab suit-tab-accountinvoice'
1440 dollar_amount = right_dollar_field("amount", "Amount")
1442 class PendingChargeInline(PlStackTabularInline):
1445 verbose_name_plural = "Charges"
1446 verbose_name = "Charge"
1447 exclude = ["invoice"]
1448 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1449 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1450 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1454 def queryset(self, request):
1455 qs = super(PendingChargeInline, self).queryset(request)
1456 qs = qs.filter(state="pending")
1459 dollar_amount = right_dollar_field("amount", "Amount")
1461 class PaymentInline(PlStackTabularInline):
1464 verbose_name_plural = "Payments"
1465 verbose_name = "Payment"
1466 fields = ["date", "dollar_amount"]
1467 readonly_fields = ["date", "dollar_amount"]
1468 suit_classes = 'suit-tab suit-tab-accountpayments'
1472 dollar_amount = right_dollar_field("amount", "Amount")
1474 class AccountAdmin(admin.ModelAdmin):
1475 list_display = ("site", "balance_due")
1477 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1480 (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1482 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1485 ('general','Account Details'),
1486 ('accountinvoice', 'Invoices'),
1487 ('accountpayments', 'Payments'),
1488 ('accountpendingcharges','Pending Charges'),
1491 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1492 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1493 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1496 # Now register the new UserAdmin...
1497 admin.site.register(User, UserAdmin)
1498 # ... and, since we're not using Django's builtin permissions,
1499 # unregister the Group model from admin.
1500 #admin.site.unregister(Group)
1502 #Do not show django evolution in the admin interface
1503 from django_evolution.models import Version, Evolution
1504 #admin.site.unregister(Version)
1505 #admin.site.unregister(Evolution)
1508 # When debugging it is often easier to see all the classes, but for regular use
1509 # only the top-levels should be displayed
1512 admin.site.register(Deployment, DeploymentAdmin)
1513 admin.site.register(Site, SiteAdmin)
1514 admin.site.register(Slice, SliceAdmin)
1515 admin.site.register(Service, ServiceAdmin)
1516 admin.site.register(Reservation, ReservationAdmin)
1517 admin.site.register(Network, NetworkAdmin)
1518 admin.site.register(Router, RouterAdmin)
1519 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1520 admin.site.register(Account, AccountAdmin)
1521 admin.site.register(Invoice, InvoiceAdmin)
1524 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1525 admin.site.register(ServiceClass, ServiceClassAdmin)
1526 #admin.site.register(PlanetStack)
1527 admin.site.register(Tag, TagAdmin)
1528 admin.site.register(DeploymentRole)
1529 admin.site.register(SiteRole)
1530 admin.site.register(SliceRole)
1531 admin.site.register(PlanetStackRole)
1532 admin.site.register(Node, NodeAdmin)
1533 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1534 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1535 admin.site.register(Sliver, SliverAdmin)
1536 admin.site.register(Image, ImageAdmin)
1537 admin.site.register(DashboardView, DashboardViewAdmin)