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 byNetworkName = {} # class variable
206 def __init__(self, name):
207 self.short_description = name
209 self.network_name = name
211 def __call__(self, obj):
213 for nbs in obj.networksliver_set.all():
214 if (nbs.network.name == self.network_name):
219 return self.network_name
222 def get(network_name):
223 """ We want to make sure we alwars return the same NetworkLookerUpper
224 because sometimes django will cause them to be instantiated multiple
225 times (and we don't want different ones in form.fields vs
226 SliverInline.readonly_fields).
228 if network_name not in NetworkLookerUpper.byNetworkName:
229 NetworkLookerUpper.byNetworkName[network_name] = NetworkLookerUpper(network_name)
230 return NetworkLookerUpper.byNetworkName[network_name]
232 class SliverROInline(ReadOnlyTabularInline):
234 fields = ['ip', 'instance_name', 'slice', 'numberCores', 'deploymentNetwork', 'image', 'node']
235 suit_classes = 'suit-tab suit-tab-slivers'
237 class SliverInline(PlStackTabularInline):
239 fields = ['all_ips_string', 'instance_name', 'slice', 'numberCores', 'deploymentNetwork', 'image', 'node']
241 readonly_fields = ['all_ips_string', 'instance_name']
242 suit_classes = 'suit-tab suit-tab-slivers'
244 def queryset(self, request):
245 return Sliver.select_by_user(request.user)
247 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
248 if db_field.name == 'deploymentNetwork':
249 kwargs['queryset'] = Deployment.select_by_acl(request.user)
250 # the inscrutable jquery selector below says:
251 # find the closest parent "tr" to the current element
252 # then find the child with class "field-node"
253 # then find the child with that is a select
255 kwargs['widget'] = forms.Select(attrs={'onChange': "update_nodes(this, $($(this).closest('tr')[0]).find('.field-node select')[0].id)"})
256 #kwargs['widget'] = forms.Select(attrs={'onChange': "console.log($($($(this).closest('tr')[0]).children('.field-node')[0]).children('select')[0].id);"})
258 field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
263 SMBAKER: This is the old code that implemented each network type as a
264 separate column in the sliver table.
266 def _declared_fieldsets(self):
267 # Return None so django will call get_fieldsets and we can insert our
271 def get_readonly_fields(self, request, obj=None):
272 readonly_fields = list(super(SliverInline, self).get_readonly_fields(request, obj))
274 # Lookup the networks that are bound to the slivers, and add those
275 # network names to the list of readonly fields.
277 for sliver in obj.slivers.all():
278 for nbs in sliver.networksliver_set.all():
280 network_name = nbs.network.name
281 if network_name not in [str(x) for x in readonly_fields]:
282 readonly_fields.append(NetworkLookerUpper.get(network_name))
284 return readonly_fields
286 def get_fieldsets(self, request, obj=None):
287 form = self.get_formset(request, obj).form
288 # fields = the read/write files + the read-only fields
289 fields = list(self.fields)
290 for fieldName in self.get_readonly_fields(request,obj):
291 if not fieldName in fields:
292 fields.append(fieldName)
294 return [(None, {'fields': fields})]
297 class SiteROInline(ReadOnlyTabularInline):
300 fields = ['name', 'login_base', 'site_url', 'enabled']
301 suit_classes = 'suit-tab suit-tab-sites'
303 class SiteInline(PlStackTabularInline):
306 suit_classes = 'suit-tab suit-tab-sites'
308 def queryset(self, request):
309 return Site.select_by_user(request.user)
311 class UserROInline(ReadOnlyTabularInline):
313 fields = ['email', 'firstname', 'lastname']
315 suit_classes = 'suit-tab suit-tab-users'
317 class UserInline(PlStackTabularInline):
319 fields = ['email', 'firstname', 'lastname']
321 suit_classes = 'suit-tab suit-tab-users'
323 def queryset(self, request):
324 return User.select_by_user(request.user)
326 class SliceROInline(ReadOnlyTabularInline):
328 suit_classes = 'suit-tab suit-tab-slices'
329 fields = ['name','site', 'serviceClass', 'service']
331 class SliceInline(PlStackTabularInline):
333 fields = ['name','site', 'serviceClass', 'service']
335 suit_classes = 'suit-tab suit-tab-slices'
337 def queryset(self, request):
338 return Slice.select_by_user(request.user)
340 class NodeROInline(ReadOnlyTabularInline):
343 suit_classes = 'suit-tab suit-tab-nodes'
344 fields = ['name','deployment','site']
346 class NodeInline(PlStackTabularInline):
349 suit_classes = 'suit-tab suit-tab-nodes'
350 fields = ['name','deployment','site']
352 class DeploymentPrivilegeROInline(ReadOnlyTabularInline):
353 model = DeploymentPrivilege
355 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
356 fields = ['user','role','deployment']
358 class DeploymentPrivilegeInline(PlStackTabularInline):
359 model = DeploymentPrivilege
361 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
362 fields = ['user','role','deployment']
364 def queryset(self, request):
365 return DeploymentPrivilege.select_by_user(request.user)
367 #CLEANUP DOUBLE SitePrivilegeInline
368 class SitePrivilegeROInline(ReadOnlyTabularInline):
369 model = SitePrivilege
371 suit_classes = 'suit-tab suit-tab-siteprivileges'
372 fields = ['user','site', 'role']
374 class SitePrivilegeInline(PlStackTabularInline):
375 model = SitePrivilege
377 suit_classes = 'suit-tab suit-tab-siteprivileges'
378 fields = ['user','site', 'role']
380 def formfield_for_foreignkey(self, db_field, request, **kwargs):
381 if db_field.name == 'site':
382 kwargs['queryset'] = Site.select_by_user(request.user)
384 if db_field.name == 'user':
385 kwargs['queryset'] = User.select_by_user(request.user)
386 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
388 def queryset(self, request):
389 return SitePrivilege.select_by_user(request.user)
391 class SiteDeploymentROInline(ReadOnlyTabularInline):
392 model = SiteDeployments
393 #model = Site.deployments.through
395 suit_classes = 'suit-tab suit-tab-deployments'
396 fields = ['deployment','site']
398 class SiteDeploymentInline(PlStackTabularInline):
399 model = SiteDeployments
400 #model = Site.deployments.through
402 suit_classes = 'suit-tab suit-tab-deployments'
403 fields = ['deployment','site']
405 def formfield_for_foreignkey(self, db_field, request, **kwargs):
406 if db_field.name == 'site':
407 kwargs['queryset'] = Site.select_by_user(request.user)
409 if db_field.name == 'deployment':
410 kwargs['queryset'] = Deployment.select_by_user(request.user)
411 return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
413 def queryset(self, request):
414 return SiteDeployments.select_by_user(request.user)
417 class SlicePrivilegeROInline(ReadOnlyTabularInline):
418 model = SlicePrivilege
420 suit_classes = 'suit-tab suit-tab-sliceprivileges'
421 fields = ['user', 'slice', 'role']
423 class SlicePrivilegeInline(PlStackTabularInline):
424 model = SlicePrivilege
425 suit_classes = 'suit-tab suit-tab-sliceprivileges'
427 fields = ('user', 'slice','role')
429 def formfield_for_foreignkey(self, db_field, request, **kwargs):
430 if db_field.name == 'slice':
431 kwargs['queryset'] = Slice.select_by_user(request.user)
432 if db_field.name == 'user':
433 kwargs['queryset'] = User.select_by_user(request.user)
435 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
437 def queryset(self, request):
438 return SlicePrivilege.select_by_user(request.user)
440 class SliceNetworkROInline(ReadOnlyTabularInline):
441 model = Network.slices.through
443 verbose_name = "Network Connection"
444 verbose_name_plural = "Network Connections"
445 suit_classes = 'suit-tab suit-tab-slicenetworks'
448 class SliceNetworkInline(PlStackTabularInline):
449 model = Network.slices.through
450 selflink_fieldname = "network"
452 verbose_name = "Network Connection"
453 verbose_name_plural = "Network Connections"
454 suit_classes = 'suit-tab suit-tab-slicenetworks'
457 class ImageDeploymentsROInline(ReadOnlyTabularInline):
458 model = ImageDeployments
460 verbose_name = "Image Deployments"
461 verbose_name_plural = "Image Deployments"
462 suit_classes = 'suit-tab suit-tab-imagedeployments'
463 fields = ['image', 'deployment', 'glance_image_id']
465 class ImageDeploymentsInline(PlStackTabularInline):
466 model = ImageDeployments
468 verbose_name = "Image Deployments"
469 verbose_name_plural = "Image Deployments"
470 suit_classes = 'suit-tab suit-tab-imagedeployments'
471 fields = ['image', 'deployment', 'glance_image_id']
472 readonly_fields = ['glance_image_id']
474 class PlainTextWidget(forms.HiddenInput):
475 input_type = 'hidden'
477 def render(self, name, value, attrs=None):
480 return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
482 class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
485 def save_model(self, request, obj, form, change):
486 obj.caller = request.user
487 # update openstack connection to use this site/tenant
488 obj.save_by_user(request.user)
490 def delete_model(self, request, obj):
491 obj.delete_by_user(request.user)
493 def save_formset(self, request, form, formset, change):
494 instances = formset.save(commit=False)
495 for instance in instances:
496 instance.save_by_user(request.user)
499 class SliceRoleAdmin(PlanetStackBaseAdmin):
503 class SiteRoleAdmin(PlanetStackBaseAdmin):
507 class DeploymentAdminForm(forms.ModelForm):
508 sites = forms.ModelMultipleChoiceField(
509 queryset=Site.objects.all(),
511 help_text="Select which sites are allowed to host nodes in this deployment",
512 widget=FilteredSelectMultiple(
513 verbose_name=('Sites'), is_stacked=False
516 images = forms.ModelMultipleChoiceField(
517 queryset=Image.objects.all(),
519 help_text="Select which images should be deployed on this deployment",
520 widget=FilteredSelectMultiple(
521 verbose_name=('Images'), is_stacked=False
527 def __init__(self, *args, **kwargs):
528 request = kwargs.pop('request', None)
529 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
531 self.fields['accessControl'].initial = "allow site " + request.user.site.name
533 if self.instance and self.instance.pk:
534 self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments_set.all()]
535 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments_set.all()]
537 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
538 """ helper function for handling m2m relations from the MultipleChoiceField
540 this_obj: the source object we want to link from
542 selected_objs: a list of destination objects we want to link to
544 all_relations: the full set of relations involving this_obj, including ones we don't want
546 relation_class: the class that implements the relation from source to dest
548 local_attrname: field name representing this_obj in relation_class
550 foreign_attrname: field name representing selected_objs in relation_class
552 This function will remove all newobjclass relations from this_obj
553 that are not contained in selected_objs, and add any relations that
554 are in selected_objs but don't exist in the data model yet.
557 existing_dest_objs = []
558 for relation in list(all_relations):
559 if getattr(relation, foreign_attrname) not in selected_objs:
560 #print "deleting site", sdp.site
563 existing_dest_objs.append(getattr(relation, foreign_attrname))
565 for dest_obj in selected_objs:
566 if dest_obj not in existing_dest_objs:
567 #print "adding site", site
568 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
569 relation = relation_class(**kwargs)
572 def save(self, commit=True):
573 deployment = super(DeploymentAdminForm, self).save(commit=False)
579 # save_m2m() doesn't seem to work with 'through' relations. So we
580 # create/destroy the through models ourselves. There has to be
583 self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments_set.all(), SiteDeployments, "deployment", "site")
584 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments_set.all(), ImageDeployments, "deployment", "image")
590 class DeploymentAdminROForm(DeploymentAdminForm):
591 def save(self, commit=True):
592 raise PermissionDenied
594 class SiteAssocInline(PlStackTabularInline):
595 model = Site.deployments.through
597 suit_classes = 'suit-tab suit-tab-sites'
599 class DeploymentAdmin(PlanetStackBaseAdmin):
601 fieldList = ['name','sites', 'images', 'accessControl']
602 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
603 inlines = [DeploymentPrivilegeInline,NodeInline,TagInline] # ,ImageDeploymentsInline]
605 user_readonly_inlines = [DeploymentPrivilegeROInline,NodeROInline,TagROInline] # ,ImageDeploymentsROInline]
606 user_readonly_fields = ['name']
608 suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags')) # ,('imagedeployments','Images'))
610 def get_form(self, request, obj=None, **kwargs):
611 if request.user.isReadOnlyUser():
612 kwargs["form"] = DeploymentAdminROForm
614 kwargs["form"] = DeploymentAdminForm
615 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
617 # from stackexchange: pass the request object into the form
619 class AdminFormMetaClass(adminForm):
620 def __new__(cls, *args, **kwargs):
621 kwargs['request'] = request
622 return adminForm(*args, **kwargs)
624 return AdminFormMetaClass
626 class ServiceAttrAsTabROInline(ReadOnlyTabularInline):
627 model = ServiceAttribute
628 fields = ['name','value']
630 suit_classes = 'suit-tab suit-tab-serviceattrs'
632 class ServiceAttrAsTabInline(PlStackTabularInline):
633 model = ServiceAttribute
634 fields = ['name','value']
636 suit_classes = 'suit-tab suit-tab-serviceattrs'
638 class ServiceAdmin(PlanetStackBaseAdmin):
639 list_display = ("name","description","versionNumber","enabled","published")
640 fieldList = ["name","description","versionNumber","enabled","published"]
641 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
642 inlines = [ServiceAttrAsTabInline,SliceInline]
644 user_readonly_fields = fieldList
645 user_readonly_inlines = [ServiceAttrAsTabROInline,SliceROInline]
647 suit_form_tabs =(('general', 'Service Details'),
649 ('serviceattrs','Additional Attributes'),
652 class SiteAdmin(PlanetStackBaseAdmin):
653 fieldList = ['name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
655 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
656 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
658 suit_form_tabs =(('general', 'Site Details'),
660 ('siteprivileges','Privileges'),
661 ('deployments','Deployments'),
666 readonly_fields = ['accountLink']
668 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
669 user_readonly_inlines = [SliceROInline,UserROInline,TagROInline, NodeROInline, SitePrivilegeROInline,SiteDeploymentROInline]
671 list_display = ('name', 'login_base','site_url', 'enabled')
672 filter_horizontal = ('deployments',)
673 inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentInline]
674 search_fields = ['name']
676 def queryset(self, request):
677 return Site.select_by_user(request.user)
679 def get_formsets(self, request, obj=None):
680 for inline in self.get_inline_instances(request, obj):
681 # hide MyInline in the add view
684 if isinstance(inline, SliceInline):
685 inline.model.caller = request.user
686 yield inline.get_formset(request, obj)
688 def get_formsets(self, request, obj=None):
689 for inline in self.get_inline_instances(request, obj):
690 # hide MyInline in the add view
693 if isinstance(inline, SliverInline):
694 inline.model.caller = request.user
695 yield inline.get_formset(request, obj)
697 def accountLink(self, obj):
698 link_obj = obj.accounts.all()
700 reverse_path = "admin:core_account_change"
701 url = reverse(reverse_path, args =(link_obj[0].id,))
702 return "<a href='%s'>%s</a>" % (url, "view billing details")
704 return "no billing data for this site"
705 accountLink.allow_tags = True
706 accountLink.short_description = "Billing"
708 def save_model(self, request, obj, form, change):
709 # update openstack connection to use this site/tenant
710 obj.save_by_user(request.user)
712 def delete_model(self, request, obj):
713 obj.delete_by_user(request.user)
716 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
717 fieldList = ['user', 'site', 'role']
719 (None, {'fields': fieldList, 'classes':['collapse']})
721 list_display = ('user', 'site', 'role')
722 user_readonly_fields = fieldList
723 user_readonly_inlines = []
725 def formfield_for_foreignkey(self, db_field, request, **kwargs):
726 if db_field.name == 'site':
727 if not request.user.is_admin:
728 # only show sites where user is an admin or pi
730 for site_privilege in SitePrivilege.objects.filer(user=request.user):
731 if site_privilege.role.role_type in ['admin', 'pi']:
732 sites.add(site_privilege.site)
733 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
735 if db_field.name == 'user':
736 if not request.user.is_admin:
737 # only show users from sites where caller has admin or pi role
738 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
739 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
740 sites = [site_privilege.site for site_privilege in site_privileges]
741 site_privileges = SitePrivilege.objects.filter(site__in=sites)
742 emails = [site_privilege.user.email for site_privilege in site_privileges]
743 users = User.objects.filter(email__in=emails)
744 kwargs['queryset'] = users
746 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
748 def queryset(self, request):
749 # admins can see all privileges. Users can only see privileges at sites
750 # where they have the admin role or pi role.
751 qs = super(SitePrivilegeAdmin, self).queryset(request)
752 #if not request.user.is_admin:
753 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
754 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
755 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
756 # sites = Site.objects.filter(login_base__in=login_bases)
757 # qs = qs.filter(site__in=sites)
760 class SliceForm(forms.ModelForm):
764 'service': LinkedSelect
767 class SliceAdmin(PlanetStackBaseAdmin):
769 fieldList = ['name', 'site', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
770 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
771 list_display = ('name', 'site','serviceClass', 'slice_url', 'max_slivers')
772 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
774 user_readonly_fields = fieldList
775 user_readonly_inlines = [SlicePrivilegeROInline,SliverROInline,TagROInline, ReservationROInline, SliceNetworkROInline]
777 suit_form_tabs =(('general', 'Slice Details'),
778 ('slicenetworks','Networks'),
779 ('sliceprivileges','Privileges'),
780 ('slivers','Slivers'),
782 ('reservations','Reservations'),
785 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
786 #deployment_nodes = {}
787 #for node in Node.objects.all():
788 # deployment_nodes[node.deployment.id] = get(deployment_nodes, node.deployment.id, []).append( (node.id, node.name) )
790 deployment_nodes = []
791 for node in Node.objects.all():
792 deployment_nodes.append( (node.deployment.id, node.id, node.name) )
794 context["deployment_nodes"] = deployment_nodes
796 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
798 def formfield_for_foreignkey(self, db_field, request, **kwargs):
799 if db_field.name == 'site':
800 kwargs['queryset'] = Site.select_by_user(request.user)
802 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
804 def queryset(self, request):
805 # admins can see all keys. Users can only see slices they belong to.
806 return Slice.select_by_user(request.user)
808 def get_formsets(self, request, obj=None):
809 for inline in self.get_inline_instances(request, obj):
810 # hide MyInline in the add view
813 if isinstance(inline, SliverInline):
814 inline.model.caller = request.user
815 yield inline.get_formset(request, obj)
818 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
820 (None, {'fields': ['user', 'slice', 'role']})
822 list_display = ('user', 'slice', 'role')
824 user_readonly_fields = ['user', 'slice', 'role']
825 user_readonly_inlines = []
827 def formfield_for_foreignkey(self, db_field, request, **kwargs):
828 if db_field.name == 'slice':
829 kwargs['queryset'] = Slice.select_by_user(request.user)
831 if db_field.name == 'user':
832 kwargs['queryset'] = User.select_by_user(request.user)
834 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
836 def queryset(self, request):
837 # admins can see all memberships. Users can only see memberships of
838 # slices where they have the admin role.
839 return SlicePrivilege.select_by_user(request.user)
841 def save_model(self, request, obj, form, change):
842 # update openstack connection to use this site/tenant
843 auth = request.session.get('auth', {})
844 auth['tenant'] = obj.slice.slicename
845 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
848 def delete_model(self, request, obj):
849 # update openstack connection to use this site/tenant
850 auth = request.session.get('auth', {})
851 auth['tenant'] = obj.slice.slicename
852 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
856 class ImageAdmin(PlanetStackBaseAdmin):
858 fieldsets = [('Image Details',
859 {'fields': ['name', 'disk_format', 'container_format'],
860 'classes': ['suit-tab suit-tab-general']})
863 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
865 inlines = [SliverInline, ImageDeploymentsInline]
867 user_readonly_fields = ['name', 'disk_format', 'container_format']
868 user_readonly_inlines = [SliverROInline, ImageDeploymentsROInline]
870 class NodeForm(forms.ModelForm):
873 'site': LinkedSelect,
874 'deployment': LinkedSelect
877 class NodeAdmin(PlanetStackBaseAdmin):
879 list_display = ('name', 'site', 'deployment')
880 list_filter = ('deployment',)
882 inlines = [TagInline,SliverInline]
883 fieldsets = [('Node Details', {'fields': ['name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
885 user_readonly_fields = ['name','site','deployment']
886 user_readonly_inlines = [TagInline,SliverInline]
888 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
891 class SliverForm(forms.ModelForm):
894 ip = forms.CharField(widget=PlainTextWidget)
895 instance_name = forms.CharField(widget=PlainTextWidget)
897 'ip': PlainTextWidget(),
898 'instance_name': PlainTextWidget(),
899 'slice': LinkedSelect,
900 'deploymentNetwork': LinkedSelect,
901 'node': LinkedSelect,
902 'image': LinkedSelect
905 class TagAdmin(PlanetStackBaseAdmin):
906 list_display = ['service', 'name', 'value', 'content_type', 'content_object',]
907 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
908 user_readonly_inlines = []
910 class SliverAdmin(PlanetStackBaseAdmin):
913 ('Sliver Details', {'fields': ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
915 list_display = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']
917 suit_form_tabs =(('general', 'Sliver Details'),
921 inlines = [TagInline]
923 user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image']
924 user_readonly_inlines = [TagROInline]
926 def formfield_for_foreignkey(self, db_field, request, **kwargs):
927 if db_field.name == 'slice':
928 kwargs['queryset'] = Slice.select_by_user(request.user)
930 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
932 def queryset(self, request):
933 # admins can see all slivers. Users can only see slivers of
934 # the slices they belong to.
935 return Sliver.select_by_user(request.user)
938 def get_formsets(self, request, obj=None):
939 # make some fields read only if we are updating an existing record
941 #self.readonly_fields = ('ip', 'instance_name')
942 self.readonly_fields = ()
944 self.readonly_fields = ()
945 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
947 for inline in self.get_inline_instances(request, obj):
948 # hide MyInline in the add view
951 if isinstance(inline, SliverInline):
952 inline.model.caller = request.user
953 yield inline.get_formset(request, obj)
955 #def save_model(self, request, obj, form, change):
956 # # update openstack connection to use this site/tenant
957 # auth = request.session.get('auth', {})
958 # auth['tenant'] = obj.slice.name
959 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
960 # obj.creator = request.user
963 #def delete_model(self, request, obj):
964 # # update openstack connection to use this site/tenant
965 # auth = request.session.get('auth', {})
966 # auth['tenant'] = obj.slice.name
967 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
970 class UserCreationForm(forms.ModelForm):
971 """A form for creating new users. Includes all the required
972 fields, plus a repeated password."""
973 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
974 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
978 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
980 def clean_password2(self):
981 # Check that the two password entries match
982 password1 = self.cleaned_data.get("password1")
983 password2 = self.cleaned_data.get("password2")
984 if password1 and password2 and password1 != password2:
985 raise forms.ValidationError("Passwords don't match")
988 def save(self, commit=True):
989 # Save the provided password in hashed format
990 user = super(UserCreationForm, self).save(commit=False)
991 user.password = self.cleaned_data["password1"]
992 #user.set_password(self.cleaned_data["password1"])
998 class UserChangeForm(forms.ModelForm):
999 """A form for updating users. Includes all the fields on
1000 the user, but replaces the password field with admin's
1001 password hash display field.
1003 password = ReadOnlyPasswordHashField(label='Password',
1004 help_text= '<a href=\"password/\">Change Password</a>.')
1009 def clean_password(self):
1010 # Regardless of what the user provides, return the initial value.
1011 # This is done here, rather than on the field, because the
1012 # field does not have access to the initial value
1013 return self.initial["password"]
1015 class UserDashboardViewInline(PlStackTabularInline):
1016 model = UserDashboardView
1018 suit_classes = 'suit-tab suit-tab-dashboards'
1019 fields = ['user', 'dashboardView', 'order']
1021 class UserDashboardViewROInline(ReadOnlyTabularInline):
1022 model = UserDashboardView
1024 suit_classes = 'suit-tab suit-tab-dashboards'
1025 fields = ['user', 'dashboardView', 'order']
1027 class UserAdmin(UserAdmin):
1031 # The forms to add and change user instances
1032 form = UserChangeForm
1033 add_form = UserCreationForm
1035 # The fields to be used in displaying the User model.
1036 # These override the definitions on the base UserAdmin
1037 # that reference specific fields on auth.User.
1038 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
1039 list_filter = ('site',)
1040 inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
1042 fieldListLoginDetails = ['email','site','password','is_active','is_readonly','is_admin','public_key']
1043 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1046 ('Login Details', {'fields': ['email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1047 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1048 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1049 #('Important dates', {'fields': ('last_login',)}),
1053 'classes': ('wide',),
1054 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
1057 search_fields = ('email',)
1058 ordering = ('email',)
1059 filter_horizontal = ()
1061 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1062 user_readonly_inlines = [SlicePrivilegeROInline,SitePrivilegeROInline,DeploymentPrivilegeROInline,UserDashboardViewROInline]
1064 suit_form_tabs =(('general','Login Details'),
1065 ('contact','Contact Information'),
1066 ('sliceprivileges','Slice Privileges'),
1067 ('siteprivileges','Site Privileges'),
1068 ('deploymentprivileges','Deployment Privileges'),
1069 ('dashboards','Dashboard Views'))
1071 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1072 if db_field.name == 'site':
1073 kwargs['queryset'] = Site.select_by_user(request.user)
1075 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1077 def has_add_permission(self, request, obj=None):
1078 return (not self.__user_is_readonly(request))
1080 def has_delete_permission(self, request, obj=None):
1081 return (not self.__user_is_readonly(request))
1083 def get_actions(self,request):
1084 actions = super(UserAdmin,self).get_actions(request)
1086 if self.__user_is_readonly(request):
1087 if 'delete_selected' in actions:
1088 del actions['delete_selected']
1092 def change_view(self,request,object_id, extra_context=None):
1094 if self.__user_is_readonly(request):
1095 if not hasattr(self, "readonly_save"):
1096 # save the original readonly fields
\r
1097 self.readonly_save = self.readonly_fields
\r
1098 self.inlines_save = self.inlines
1099 self.readonly_fields=self.user_readonly_fields
1100 self.inlines = self.user_readonly_inlines
1102 if hasattr(self, "readonly_save"):
\r
1103 # restore the original readonly fields
\r
1104 self.readonly_fields = self.readonly_save
\r
1105 self.inlines = self.inlines_save
1108 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1109 except PermissionDenied:
1111 if request.method == 'POST':
1112 raise PermissionDenied
1113 request.readonly = True
1114 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1116 def __user_is_readonly(self, request):
1117 #groups = [x.name for x in request.user.groups.all() ]
1118 #return "readonly" in groups
1119 return request.user.isReadOnlyUser()
1121 def queryset(self, request):
1122 return User.select_by_user(request.user)
1124 class DashboardViewAdmin(PlanetStackBaseAdmin):
1125 fieldsets = [('Dashboard View Details',
1126 {'fields': ['name', 'url'],
1127 'classes': ['suit-tab suit-tab-general']})
1130 suit_form_tabs =(('general','Dashboard View Details'),)
1132 class ServiceResourceROInline(ReadOnlyTabularInline):
1133 model = ServiceResource
1135 fields = ['serviceClass', 'name', 'maxUnitsDeployment', 'maxUnitsNode', 'maxDuration', 'bucketInRate', 'bucketMaxSize', 'cost', 'calendarReservable']
1137 class ServiceResourceInline(PlStackTabularInline):
1138 model = ServiceResource
1141 class ServiceClassAdmin(PlanetStackBaseAdmin):
1142 list_display = ('name', 'commitment', 'membershipFee')
1143 inlines = [ServiceResourceInline]
1145 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1146 user_readonly_inlines = []
1148 class ReservedResourceROInline(ReadOnlyTabularInline):
1149 model = ReservedResource
1151 fields = ['sliver', 'resource','quantity','reservationSet']
1152 suit_classes = 'suit-tab suit-tab-reservedresources'
1154 class ReservedResourceInline(PlStackTabularInline):
1155 model = ReservedResource
1157 suit_classes = 'suit-tab suit-tab-reservedresources'
1159 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1160 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1162 if db_field.name == 'resource':
1163 # restrict resources to those that the slice's service class allows
1164 if request._slice is not None:
1165 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1166 if len(field.queryset) > 0:
1167 field.initial = field.queryset.all()[0]
1169 field.queryset = field.queryset.none()
\r
1170 elif db_field.name == 'sliver':
\r
1171 # restrict slivers to those that belong to the slice
\r
1172 if request._slice is not None:
\r
1173 field.queryset = field.queryset.filter(slice = request._slice)
1175 field.queryset = field.queryset.none()
\r
1179 def queryset(self, request):
1180 return ReservedResource.select_by_user(request.user)
1182 class ReservationChangeForm(forms.ModelForm):
1186 'slice' : LinkedSelect
1189 class ReservationAddForm(forms.ModelForm):
1190 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1191 refresh = forms.CharField(widget=forms.HiddenInput())
1194 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1196 def clean_slice(self):
1197 slice = self.cleaned_data.get("slice")
1198 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1200 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1206 'slice' : LinkedSelect
1210 class ReservationAddRefreshForm(ReservationAddForm):
1211 """ This form is displayed when the Reservation Form receives an update
1212 from the Slice dropdown onChange handler. It doesn't validate the
1213 data and doesn't save the data. This will cause the form to be
1217 """ don't validate anything other than slice """
1218 dont_validate_fields = ("startTime", "duration")
1220 def full_clean(self):
1221 result = super(ReservationAddForm, self).full_clean()
1223 for fieldname in self.dont_validate_fields:
1224 if fieldname in self._errors:
1225 del self._errors[fieldname]
1229 """ don't save anything """
1233 class ReservationAdmin(PlanetStackBaseAdmin):
1234 fieldList = ['slice', 'startTime', 'duration']
1235 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1236 list_display = ('startTime', 'duration')
1237 form = ReservationAddForm
1239 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1241 inlines = [ReservedResourceInline]
1242 user_readonly_inlines = [ReservedResourceROInline]
1243 user_readonly_fields = fieldList
1245 def add_view(self, request, form_url='', extra_context=None):
1246 timezone.activate(request.user.timezone)
1247 request._refresh = False
1248 request._slice = None
1249 if request.method == 'POST':
1250 # "refresh" will be set to "1" if the form was submitted due to
1251 # a change in the Slice dropdown.
1252 if request.POST.get("refresh","1") == "1":
1253 request._refresh = True
1254 request.POST["refresh"] = "0"
1256 # Keep track of the slice that was selected, so the
1257 # reservedResource inline can filter items for the slice.
1258 request._slice = request.POST.get("slice",None)
1259 if (request._slice is not None):
1260 request._slice = Slice.objects.get(id=request._slice)
1262 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1265 def changelist_view(self, request, extra_context = None):
1266 timezone.activate(request.user.timezone)
1267 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1269 def get_form(self, request, obj=None, **kwargs):
1272 # For changes, set request._slice to the slice already set in the
1274 request._slice = obj.slice
1275 self.form = ReservationChangeForm
1277 if getattr(request, "_refresh", False):
1278 self.form = ReservationAddRefreshForm
1280 self.form = ReservationAddForm
1281 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1283 def get_readonly_fields(self, request, obj=None):
1284 if (obj is not None):
1285 # Prevent slice from being changed after the reservation has been
1291 def queryset(self, request):
1292 return Reservation.select_by_user(request.user)
1294 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1295 list_display = ("name", )
1296 user_readonly_fields = ['name']
1297 user_readonly_inlines = []
1299 class RouterAdmin(PlanetStackBaseAdmin):
1300 list_display = ("name", )
1301 user_readonly_fields = ['name']
1302 user_readonly_inlines = []
1304 class RouterROInline(ReadOnlyTabularInline):
1305 model = Router.networks.through
1307 verbose_name_plural = "Routers"
1308 verbose_name = "Router"
1309 suit_classes = 'suit-tab suit-tab-routers'
1311 fields = ['name', 'owner', 'permittedNetworks', 'networks']
1313 class RouterInline(PlStackTabularInline):
1314 model = Router.networks.through
1316 verbose_name_plural = "Routers"
1317 verbose_name = "Router"
1318 suit_classes = 'suit-tab suit-tab-routers'
1320 class NetworkParameterROInline(ReadOnlyTabularInline):
1321 model = NetworkParameter
1323 verbose_name_plural = "Parameters"
1324 verbose_name = "Parameter"
1325 suit_classes = 'suit-tab suit-tab-netparams'
1326 fields = ['parameter', 'value', 'content_type', 'object_id', 'content_object']
1328 class NetworkParameterInline(generic.GenericTabularInline):
1329 model = NetworkParameter
1331 verbose_name_plural = "Parameters"
1332 verbose_name = "Parameter"
1333 suit_classes = 'suit-tab suit-tab-netparams'
1335 class NetworkSliversROInline(ReadOnlyTabularInline):
1336 fields = ['network', 'sliver', 'ip', 'port_id']
1337 model = NetworkSliver
1339 verbose_name_plural = "Slivers"
1340 verbose_name = "Sliver"
1341 suit_classes = 'suit-tab suit-tab-networkslivers'
1343 class NetworkSliversInline(PlStackTabularInline):
1344 readonly_fields = ("ip", )
1345 model = NetworkSliver
1346 selflink_fieldname = "sliver"
1348 verbose_name_plural = "Slivers"
1349 verbose_name = "Sliver"
1350 suit_classes = 'suit-tab suit-tab-networkslivers'
1352 class NetworkSlicesROInline(ReadOnlyTabularInline):
1353 model = NetworkSlice
1355 verbose_name_plural = "Slices"
1356 verbose_name = "Slice"
1357 suit_classes = 'suit-tab suit-tab-networkslices'
1358 fields = ['network','slice']
1360 class NetworkSlicesInline(PlStackTabularInline):
1361 model = NetworkSlice
1362 selflink_fieldname = "slice"
1364 verbose_name_plural = "Slices"
1365 verbose_name = "Slice"
1366 suit_classes = 'suit-tab suit-tab-networkslices'
1368 class NetworkAdmin(PlanetStackBaseAdmin):
1369 list_display = ("name", "subnet", "ports", "labels")
1370 readonly_fields = ("subnet", )
1372 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1375 (None, {'fields': ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet'], 'classes':['suit-tab suit-tab-general']}),]
1377 user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1378 user_readonly_inlines = [NetworkParameterROInline, NetworkSliversROInline, NetworkSlicesROInline, RouterROInline]
1381 ('general','Network Details'),
1382 ('netparams', 'Parameters'),
1383 ('networkslivers','Slivers'),
1384 ('networkslices','Slices'),
1385 ('routers','Routers'),
1387 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1388 list_display = ("name", "guaranteedBandwidth", "visibility")
1389 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1390 user_readonly_inlines = []
1392 # register a signal that caches the user's credentials when they log in
1393 def cache_credentials(sender, user, request, **kwds):
1394 auth = {'username': request.POST['username'],
1395 'password': request.POST['password']}
1396 request.session['auth'] = auth
1397 user_logged_in.connect(cache_credentials)
1399 def dollar_field(fieldName, short_description):
1400 def newFunc(self, obj):
1402 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1404 x=getattr(obj, fieldName, 0.0)
1406 newFunc.short_description = short_description
1409 def right_dollar_field(fieldName, short_description):
1410 def newFunc(self, obj):
1412 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1413 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1415 x=getattr(obj, fieldName, 0.0)
1417 newFunc.short_description = short_description
1418 newFunc.allow_tags = True
1421 class InvoiceChargeInline(PlStackTabularInline):
1424 verbose_name_plural = "Charges"
1425 verbose_name = "Charge"
1426 exclude = ['account']
1427 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1428 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1432 dollar_amount = right_dollar_field("amount", "Amount")
1434 class InvoiceAdmin(admin.ModelAdmin):
1435 list_display = ("date", "account")
1437 inlines = [InvoiceChargeInline]
1439 fields = ["date", "account", "dollar_amount"]
1440 readonly_fields = ["date", "account", "dollar_amount"]
1442 dollar_amount = dollar_field("amount", "Amount")
1444 class InvoiceInline(PlStackTabularInline):
1447 verbose_name_plural = "Invoices"
1448 verbose_name = "Invoice"
1449 fields = ["date", "dollar_amount"]
1450 readonly_fields = ["date", "dollar_amount"]
1451 suit_classes = 'suit-tab suit-tab-accountinvoice'
1455 dollar_amount = right_dollar_field("amount", "Amount")
1457 class PendingChargeInline(PlStackTabularInline):
1460 verbose_name_plural = "Charges"
1461 verbose_name = "Charge"
1462 exclude = ["invoice"]
1463 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1464 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1465 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1469 def queryset(self, request):
1470 qs = super(PendingChargeInline, self).queryset(request)
1471 qs = qs.filter(state="pending")
1474 dollar_amount = right_dollar_field("amount", "Amount")
1476 class PaymentInline(PlStackTabularInline):
1479 verbose_name_plural = "Payments"
1480 verbose_name = "Payment"
1481 fields = ["date", "dollar_amount"]
1482 readonly_fields = ["date", "dollar_amount"]
1483 suit_classes = 'suit-tab suit-tab-accountpayments'
1487 dollar_amount = right_dollar_field("amount", "Amount")
1489 class AccountAdmin(admin.ModelAdmin):
1490 list_display = ("site", "balance_due")
1492 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1495 (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1497 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1500 ('general','Account Details'),
1501 ('accountinvoice', 'Invoices'),
1502 ('accountpayments', 'Payments'),
1503 ('accountpendingcharges','Pending Charges'),
1506 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1507 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1508 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1511 # Now register the new UserAdmin...
1512 admin.site.register(User, UserAdmin)
1513 # ... and, since we're not using Django's builtin permissions,
1514 # unregister the Group model from admin.
1515 #admin.site.unregister(Group)
1517 #Do not show django evolution in the admin interface
1518 from django_evolution.models import Version, Evolution
1519 #admin.site.unregister(Version)
1520 #admin.site.unregister(Evolution)
1523 # When debugging it is often easier to see all the classes, but for regular use
1524 # only the top-levels should be displayed
1527 admin.site.register(Deployment, DeploymentAdmin)
1528 admin.site.register(Site, SiteAdmin)
1529 admin.site.register(Slice, SliceAdmin)
1530 admin.site.register(Service, ServiceAdmin)
1531 admin.site.register(Reservation, ReservationAdmin)
1532 admin.site.register(Network, NetworkAdmin)
1533 admin.site.register(Router, RouterAdmin)
1534 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1535 admin.site.register(Account, AccountAdmin)
1536 admin.site.register(Invoice, InvoiceAdmin)
1539 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1540 admin.site.register(ServiceClass, ServiceClassAdmin)
1541 #admin.site.register(PlanetStack)
1542 admin.site.register(Tag, TagAdmin)
1543 admin.site.register(DeploymentRole)
1544 admin.site.register(SiteRole)
1545 admin.site.register(SliceRole)
1546 admin.site.register(PlanetStackRole)
1547 admin.site.register(Node, NodeAdmin)
1548 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1549 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1550 admin.site.register(Sliver, SliverAdmin)
1551 admin.site.register(Image, ImageAdmin)
1552 admin.site.register(DashboardView, DashboardViewAdmin)