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_display = ('email', 'username','firstname', 'lastname', 'is_admin', 'last_login')
1025 list_filter = ('site',)
1026 inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
1028 fieldListLoginDetails = ['email','site','password','is_readonly','is_amin','public_key']
1029 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1032 ('Login Details', {'fields': ['email', 'site','password', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1033 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1034 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1035 #('Important dates', {'fields': ('last_login',)}),
1039 'classes': ('wide',),
1040 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
1043 search_fields = ('email',)
1044 ordering = ('email',)
1045 filter_horizontal = ()
1047 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1048 user_readonly_inlines = [SlicePrivilegeROInline,SitePrivilegeROInline,DeploymentPrivilegeROInline,UserDashboardViewROInline]
1050 suit_form_tabs =(('general','Login Details'),
1051 ('contact','Contact Information'),
1052 ('sliceprivileges','Slice Privileges'),
1053 ('siteprivileges','Site Privileges'),
1054 ('deploymentprivileges','Deployment Privileges'),
1055 ('dashboards','Dashboard Views'))
1057 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1058 if db_field.name == 'site':
1059 kwargs['queryset'] = Site.select_by_user(request.user)
1061 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1063 def has_add_permission(self, request, obj=None):
1064 return (not self.__user_is_readonly(request))
1066 def has_delete_permission(self, request, obj=None):
1067 return (not self.__user_is_readonly(request))
1069 def get_actions(self,request):
1070 actions = super(UserAdmin,self).get_actions(request)
1072 if self.__user_is_readonly(request):
1073 if 'delete_selected' in actions:
1074 del actions['delete_selected']
1078 def change_view(self,request,object_id, extra_context=None):
1080 if self.__user_is_readonly(request):
1081 if not hasattr(self, "readonly_save"):
1082 # save the original readonly fields
\r
1083 self.readonly_save = self.readonly_fields
\r
1084 self.inlines_save = self.inlines
1085 self.readonly_fields=self.user_readonly_fields
1086 self.inlines = self.user_readonly_inlines
1088 if hasattr(self, "readonly_save"):
\r
1089 # restore the original readonly fields
\r
1090 self.readonly_fields = self.readonly_save
\r
1091 self.inlines = self.inlines_save
1094 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1095 except PermissionDenied:
1097 if request.method == 'POST':
1098 raise PermissionDenied
1099 request.readonly = True
1100 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1102 def __user_is_readonly(self, request):
1103 #groups = [x.name for x in request.user.groups.all() ]
1104 #return "readonly" in groups
1105 return request.user.isReadOnlyUser()
1107 def queryset(self, request):
1108 return User.select_by_user(request.user)
1110 class DashboardViewAdmin(PlanetStackBaseAdmin):
1111 fieldsets = [('Dashboard View Details',
1112 {'fields': ['name', 'url'],
1113 'classes': ['suit-tab suit-tab-general']})
1116 suit_form_tabs =(('general','Dashboard View Details'),)
1118 class ServiceResourceROInline(ReadOnlyTabularInline):
1119 model = ServiceResource
1121 fields = ['serviceClass', 'name', 'maxUnitsDeployment', 'maxUnitsNode', 'maxDuration', 'bucketInRate', 'bucketMaxSize', 'cost', 'calendarReservable']
1123 class ServiceResourceInline(PlStackTabularInline):
1124 model = ServiceResource
1127 class ServiceClassAdmin(PlanetStackBaseAdmin):
1128 list_display = ('name', 'commitment', 'membershipFee')
1129 inlines = [ServiceResourceInline]
1131 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1132 user_readonly_inlines = []
1134 class ReservedResourceROInline(ReadOnlyTabularInline):
1135 model = ReservedResource
1137 fields = ['sliver', 'resource','quantity','reservationSet']
1138 suit_classes = 'suit-tab suit-tab-reservedresources'
1140 class ReservedResourceInline(PlStackTabularInline):
1141 model = ReservedResource
1143 suit_classes = 'suit-tab suit-tab-reservedresources'
1145 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1146 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1148 if db_field.name == 'resource':
1149 # restrict resources to those that the slice's service class allows
1150 if request._slice is not None:
1151 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1152 if len(field.queryset) > 0:
1153 field.initial = field.queryset.all()[0]
1155 field.queryset = field.queryset.none()
\r
1156 elif db_field.name == 'sliver':
\r
1157 # restrict slivers to those that belong to the slice
\r
1158 if request._slice is not None:
\r
1159 field.queryset = field.queryset.filter(slice = request._slice)
1161 field.queryset = field.queryset.none()
\r
1165 def queryset(self, request):
1166 return ReservedResource.select_by_user(request.user)
1168 class ReservationChangeForm(forms.ModelForm):
1172 'slice' : LinkedSelect
1175 class ReservationAddForm(forms.ModelForm):
1176 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1177 refresh = forms.CharField(widget=forms.HiddenInput())
1180 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1182 def clean_slice(self):
1183 slice = self.cleaned_data.get("slice")
1184 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1186 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1192 'slice' : LinkedSelect
1196 class ReservationAddRefreshForm(ReservationAddForm):
1197 """ This form is displayed when the Reservation Form receives an update
1198 from the Slice dropdown onChange handler. It doesn't validate the
1199 data and doesn't save the data. This will cause the form to be
1203 """ don't validate anything other than slice """
1204 dont_validate_fields = ("startTime", "duration")
1206 def full_clean(self):
1207 result = super(ReservationAddForm, self).full_clean()
1209 for fieldname in self.dont_validate_fields:
1210 if fieldname in self._errors:
1211 del self._errors[fieldname]
1215 """ don't save anything """
1219 class ReservationAdmin(PlanetStackBaseAdmin):
1220 fieldList = ['slice', 'startTime', 'duration']
1221 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1222 list_display = ('startTime', 'duration')
1223 form = ReservationAddForm
1225 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1227 inlines = [ReservedResourceInline]
1228 user_readonly_inlines = [ReservedResourceROInline]
1229 user_readonly_fields = fieldList
1231 def add_view(self, request, form_url='', extra_context=None):
1232 timezone.activate(request.user.timezone)
1233 request._refresh = False
1234 request._slice = None
1235 if request.method == 'POST':
1236 # "refresh" will be set to "1" if the form was submitted due to
1237 # a change in the Slice dropdown.
1238 if request.POST.get("refresh","1") == "1":
1239 request._refresh = True
1240 request.POST["refresh"] = "0"
1242 # Keep track of the slice that was selected, so the
1243 # reservedResource inline can filter items for the slice.
1244 request._slice = request.POST.get("slice",None)
1245 if (request._slice is not None):
1246 request._slice = Slice.objects.get(id=request._slice)
1248 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1251 def changelist_view(self, request, extra_context = None):
1252 timezone.activate(request.user.timezone)
1253 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1255 def get_form(self, request, obj=None, **kwargs):
1258 # For changes, set request._slice to the slice already set in the
1260 request._slice = obj.slice
1261 self.form = ReservationChangeForm
1263 if getattr(request, "_refresh", False):
1264 self.form = ReservationAddRefreshForm
1266 self.form = ReservationAddForm
1267 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1269 def get_readonly_fields(self, request, obj=None):
1270 if (obj is not None):
1271 # Prevent slice from being changed after the reservation has been
1277 def queryset(self, request):
1278 return Reservation.select_by_user(request.user)
1280 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1281 list_display = ("name", )
1282 user_readonly_fields = ['name']
1283 user_readonly_inlines = []
1285 class RouterAdmin(PlanetStackBaseAdmin):
1286 list_display = ("name", )
1287 user_readonly_fields = ['name']
1288 user_readonly_inlines = []
1290 class RouterROInline(ReadOnlyTabularInline):
1291 model = Router.networks.through
1293 verbose_name_plural = "Routers"
1294 verbose_name = "Router"
1295 suit_classes = 'suit-tab suit-tab-routers'
1297 fields = ['name', 'owner', 'permittedNetworks', 'networks']
1299 class RouterInline(PlStackTabularInline):
1300 model = Router.networks.through
1302 verbose_name_plural = "Routers"
1303 verbose_name = "Router"
1304 suit_classes = 'suit-tab suit-tab-routers'
1306 class NetworkParameterROInline(ReadOnlyTabularInline):
1307 model = NetworkParameter
1309 verbose_name_plural = "Parameters"
1310 verbose_name = "Parameter"
1311 suit_classes = 'suit-tab suit-tab-netparams'
1312 fields = ['parameter', 'value', 'content_type', 'object_id', 'content_object']
1314 class NetworkParameterInline(generic.GenericTabularInline):
1315 model = NetworkParameter
1317 verbose_name_plural = "Parameters"
1318 verbose_name = "Parameter"
1319 suit_classes = 'suit-tab suit-tab-netparams'
1321 class NetworkSliversROInline(ReadOnlyTabularInline):
1322 fields = ['network', 'sliver', 'ip', 'port_id']
1323 model = NetworkSliver
1325 verbose_name_plural = "Slivers"
1326 verbose_name = "Sliver"
1327 suit_classes = 'suit-tab suit-tab-networkslivers'
1329 class NetworkSliversInline(PlStackTabularInline):
1330 readonly_fields = ("ip", )
1331 model = NetworkSliver
1332 selflink_fieldname = "sliver"
1334 verbose_name_plural = "Slivers"
1335 verbose_name = "Sliver"
1336 suit_classes = 'suit-tab suit-tab-networkslivers'
1338 class NetworkSlicesROInline(ReadOnlyTabularInline):
1339 model = NetworkSlice
1341 verbose_name_plural = "Slices"
1342 verbose_name = "Slice"
1343 suit_classes = 'suit-tab suit-tab-networkslices'
1344 fields = ['network','slice']
1346 class NetworkSlicesInline(PlStackTabularInline):
1347 model = NetworkSlice
1348 selflink_fieldname = "slice"
1350 verbose_name_plural = "Slices"
1351 verbose_name = "Slice"
1352 suit_classes = 'suit-tab suit-tab-networkslices'
1354 class NetworkAdmin(PlanetStackBaseAdmin):
1355 list_display = ("name", "subnet", "ports", "labels")
1356 readonly_fields = ("subnet", )
1358 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1361 (None, {'fields': ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet'], 'classes':['suit-tab suit-tab-general']}),]
1363 user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1364 user_readonly_inlines = [NetworkParameterROInline, NetworkSliversROInline, NetworkSlicesROInline, RouterROInline]
1367 ('general','Network Details'),
1368 ('netparams', 'Parameters'),
1369 ('networkslivers','Slivers'),
1370 ('networkslices','Slices'),
1371 ('routers','Routers'),
1373 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1374 list_display = ("name", "guaranteedBandwidth", "visibility")
1375 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1376 user_readonly_inlines = []
1378 # register a signal that caches the user's credentials when they log in
1379 def cache_credentials(sender, user, request, **kwds):
1380 auth = {'username': request.POST['username'],
1381 'password': request.POST['password']}
1382 request.session['auth'] = auth
1383 user_logged_in.connect(cache_credentials)
1385 def dollar_field(fieldName, short_description):
1386 def newFunc(self, obj):
1388 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1390 x=getattr(obj, fieldName, 0.0)
1392 newFunc.short_description = short_description
1395 def right_dollar_field(fieldName, short_description):
1396 def newFunc(self, obj):
1398 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1399 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1401 x=getattr(obj, fieldName, 0.0)
1403 newFunc.short_description = short_description
1404 newFunc.allow_tags = True
1407 class InvoiceChargeInline(PlStackTabularInline):
1410 verbose_name_plural = "Charges"
1411 verbose_name = "Charge"
1412 exclude = ['account']
1413 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1414 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1418 dollar_amount = right_dollar_field("amount", "Amount")
1420 class InvoiceAdmin(admin.ModelAdmin):
1421 list_display = ("date", "account")
1423 inlines = [InvoiceChargeInline]
1425 fields = ["date", "account", "dollar_amount"]
1426 readonly_fields = ["date", "account", "dollar_amount"]
1428 dollar_amount = dollar_field("amount", "Amount")
1430 class InvoiceInline(PlStackTabularInline):
1433 verbose_name_plural = "Invoices"
1434 verbose_name = "Invoice"
1435 fields = ["date", "dollar_amount"]
1436 readonly_fields = ["date", "dollar_amount"]
1437 suit_classes = 'suit-tab suit-tab-accountinvoice'
1441 dollar_amount = right_dollar_field("amount", "Amount")
1443 class PendingChargeInline(PlStackTabularInline):
1446 verbose_name_plural = "Charges"
1447 verbose_name = "Charge"
1448 exclude = ["invoice"]
1449 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1450 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1451 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1455 def queryset(self, request):
1456 qs = super(PendingChargeInline, self).queryset(request)
1457 qs = qs.filter(state="pending")
1460 dollar_amount = right_dollar_field("amount", "Amount")
1462 class PaymentInline(PlStackTabularInline):
1465 verbose_name_plural = "Payments"
1466 verbose_name = "Payment"
1467 fields = ["date", "dollar_amount"]
1468 readonly_fields = ["date", "dollar_amount"]
1469 suit_classes = 'suit-tab suit-tab-accountpayments'
1473 dollar_amount = right_dollar_field("amount", "Amount")
1475 class AccountAdmin(admin.ModelAdmin):
1476 list_display = ("site", "balance_due")
1478 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1481 (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1483 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1486 ('general','Account Details'),
1487 ('accountinvoice', 'Invoices'),
1488 ('accountpayments', 'Payments'),
1489 ('accountpendingcharges','Pending Charges'),
1492 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1493 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1494 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1497 # Now register the new UserAdmin...
1498 admin.site.register(User, UserAdmin)
1499 # ... and, since we're not using Django's builtin permissions,
1500 # unregister the Group model from admin.
1501 #admin.site.unregister(Group)
1503 #Do not show django evolution in the admin interface
1504 from django_evolution.models import Version, Evolution
1505 #admin.site.unregister(Version)
1506 #admin.site.unregister(Evolution)
1509 # When debugging it is often easier to see all the classes, but for regular use
1510 # only the top-levels should be displayed
1513 admin.site.register(Deployment, DeploymentAdmin)
1514 admin.site.register(Site, SiteAdmin)
1515 admin.site.register(Slice, SliceAdmin)
1516 admin.site.register(Service, ServiceAdmin)
1517 admin.site.register(Reservation, ReservationAdmin)
1518 admin.site.register(Network, NetworkAdmin)
1519 admin.site.register(Router, RouterAdmin)
1520 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1521 admin.site.register(Account, AccountAdmin)
1522 admin.site.register(Invoice, InvoiceAdmin)
1525 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1526 admin.site.register(ServiceClass, ServiceClassAdmin)
1527 #admin.site.register(PlanetStack)
1528 admin.site.register(Tag, TagAdmin)
1529 admin.site.register(DeploymentRole)
1530 admin.site.register(SiteRole)
1531 admin.site.register(SliceRole)
1532 admin.site.register(PlanetStackRole)
1533 admin.site.register(Node, NodeAdmin)
1534 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1535 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1536 admin.site.register(Sliver, SliverAdmin)
1537 admin.site.register(Image, ImageAdmin)
1538 admin.site.register(DashboardView, DashboardViewAdmin)