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, AdminPasswordChangeForm
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
22 # thread locals necessary to work around a django-suit issue
23 _thread_locals = threading.local()
25 def backend_icon(obj): # backend_status, enacted, updated):
26 #return "%s %s %s" % (str(obj.updated), str(obj.enacted), str(obj.backend_status))
27 if (obj.enacted is not None) and obj.enacted >= obj.updated:
28 return '<span style="min-width:16px;"><img src="/static/admin/img/icon_success.gif"></span>'
30 if obj.backend_status == "Provisioning in progress" or obj.backend_status=="":
31 return '<span style="min-width:16px;" title="%s"><img src="/static/admin/img/icon_clock.gif"></span>' % obj.backend_status
33 return '<span style="min-width:16px;" title="%s"><img src="/static/admin/img/icon_error.gif"></span>' % obj.backend_status
35 def backend_text(obj):
36 icon = backend_icon(obj)
37 if (obj.enacted is not None) and obj.enacted >= obj.updated:
38 return "%s %s" % (icon, "successfully enacted") # enacted on %s" % str(obj.enacted))
40 return "%s %s" % (icon, obj.backend_status)
42 class PlainTextWidget(forms.HiddenInput):
45 def render(self, name, value, attrs=None):
48 return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
50 class PermissionCheckingAdminMixin(object):
51 # call save_by_user and delete_by_user instead of save and delete
53 def has_add_permission(self, request, obj=None):
54 return (not self.__user_is_readonly(request))
56 def has_delete_permission(self, request, obj=None):
57 return (not self.__user_is_readonly(request))
59 def save_model(self, request, obj, form, change):
60 if self.__user_is_readonly(request):
61 # this 'if' might be redundant if save_by_user is implemented right
62 raise PermissionDenied
64 obj.caller = request.user
65 # update openstack connection to use this site/tenant
66 obj.save_by_user(request.user)
68 def delete_model(self, request, obj):
69 obj.delete_by_user(request.user)
71 def save_formset(self, request, form, formset, change):
72 instances = formset.save(commit=False)
73 for instance in instances:
74 instance.save_by_user(request.user)
76 # BUG in django 1.7? Objects are not deleted by formset.save if
77 # commit is False. So let's delete them ourselves.
79 # code from forms/models.py save_existing_objects()
81 forms_to_delete = formset.deleted_forms
\r
82 except AttributeError:
\r
84 if formset.initial_forms:
85 for form in formset.initial_forms:
87 if form in forms_to_delete:
90 formset.deleted_objects.append(obj)
95 def get_actions(self,request):
96 actions = super(PermissionCheckingAdminMixin,self).get_actions(request)
98 if self.__user_is_readonly(request):
99 if 'delete_selected' in actions:
100 del actions['delete_selected']
104 def change_view(self,request,object_id, extra_context=None):
105 if self.__user_is_readonly(request):
106 if not hasattr(self, "readonly_save"):
\r
107 # save the original readonly fields
\r
108 self.readonly_save = self.readonly_fields
\r
109 self.inlines_save = self.inlines
\r
110 if hasattr(self, "user_readonly_fields"):
\r
111 self.readonly_fields=self.user_readonly_fields
\r
112 if hasattr(self, "user_readonly_inlines"):
\r
113 self.inlines = self.user_readonly_inlines
\r
115 if hasattr(self, "readonly_save"):
\r
116 # restore the original readonly fields
\r
117 self.readonly_fields = self.readonly_save
\r
118 if hasattr(self, "inlines_save"):
\r
119 self.inlines = self.inlines_save
122 return super(PermissionCheckingAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
123 except PermissionDenied:
125 if request.method == 'POST':
126 raise PermissionDenied
127 request.readonly = True
128 return super(PermissionCheckingAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
130 def __user_is_readonly(self, request):
131 return request.user.isReadOnlyUser()
133 def backend_status_text(self, obj):
134 return mark_safe(backend_text(obj))
136 def backend_status_icon(self, obj):
137 return mark_safe(backend_icon(obj))
138 backend_status_icon.short_description = ""
140 def get_form(self, request, obj=None):
141 # Save obj and request in thread-local storage, so suit_form_tabs can
142 # use it to determine whether we're in edit or add mode, and can
143 # determine whether the user is an admin.
144 _thread_locals.request = request
145 _thread_locals.obj = obj
146 return super(PermissionCheckingAdminMixin, self).get_form(request, obj)
148 def get_inline_instances(self, request, obj=None):
149 inlines = super(PermissionCheckingAdminMixin, self).get_inline_instances(request, obj)
151 # inlines that should only be shown to an admin user
152 if request.user.is_admin:
153 for inline_class in getattr(self, "admin_inlines", []):
154 inlines.append(inline_class(self.model, self.admin_site))
158 class ReadOnlyAwareAdmin(PermissionCheckingAdminMixin, admin.ModelAdmin):
159 # Note: Make sure PermissionCheckingAdminMixin is listed before
160 # admin.ModelAdmin in the class declaration.
164 class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
167 class SingletonAdmin (ReadOnlyAwareAdmin):
168 def has_add_permission(self, request):
169 if not super(SingletonAdmin, self).has_add_permission(request):
172 num_objects = self.model.objects.count()
178 class PlStackTabularInline(admin.TabularInline):
179 def __init__(self, *args, **kwargs):
180 super(PlStackTabularInline, self).__init__(*args, **kwargs)
182 # InlineModelAdmin as no get_fields() method, so in order to add
183 # the selflink field, we override __init__ to modify self.fields and
184 # self.readonly_fields.
186 self.setup_selflink()
188 def get_change_url(self, model, id):
189 """ Get the URL to a change form in the admin for this model """
190 reverse_path = "admin:%s_change" % (model._meta.db_table)
192 url = reverse(reverse_path, args=(id,))
193 except NoReverseMatch:
198 def setup_selflink(self):
199 if hasattr(self, "selflink_fieldname"):
200 """ self.selflink_model can be defined to punch through a relation
201 to its target object. For example, in SliceNetworkInline, set
202 selflink_model = "network", and the URL will lead to the Network
203 object instead of trying to bring up a change view of the
206 self.selflink_model = getattr(self.model,self.selflink_fieldname).field.rel.to
208 self.selflink_model = self.model
210 url = self.get_change_url(self.selflink_model, 0)
212 # We don't have an admin for this object, so don't create the
217 # Since we need to add "selflink" to the field list, we need to create
218 # self.fields if it is None.
219 if (self.fields is None):
221 for f in self.model._meta.fields:
222 if f.editable and f.name != "id":
223 self.fields.append(f.name)
225 self.fields = tuple(self.fields) + ("selflink", )
227 if self.readonly_fields is None:
228 self.readonly_fields = ()
230 self.readonly_fields = tuple(self.readonly_fields) + ("selflink", )
232 def selflink(self, obj):
233 if hasattr(self, "selflink_fieldname"):
234 obj = getattr(obj, self.selflink_fieldname)
237 url = self.get_change_url(self.selflink_model, obj.id)
238 return "<a href='%s'>Details</a>" % str(url)
240 return "Not present"
\r
242 selflink.allow_tags = True
243 selflink.short_description = "Details"
245 def has_add_permission(self, request):
246 return not request.user.isReadOnlyUser()
248 def get_readonly_fields(self, request, obj=None):
249 readonly_fields = list(self.readonly_fields)[:]
250 if request.user.isReadOnlyUser():
251 for field in self.fields:
252 if not field in readonly_fields:
253 readonly_fields.append(field)
254 return readonly_fields
256 def backend_status_icon(self, obj):
257 return mark_safe(backend_icon(obj))
258 backend_status_icon.short_description = ""
260 class PlStackGenericTabularInline(generic.GenericTabularInline):
261 def has_add_permission(self, request):
262 return not request.user.isReadOnlyUser()
264 def get_readonly_fields(self, request, obj=None):
265 readonly_fields = list(self.readonly_fields)[:]
266 if request.user.isReadOnlyUser():
267 for field in self.fields:
268 if not field in readonly_fields:
269 readonly_fields.append(field)
270 return readonly_fields
272 def backend_status_icon(self, obj):
273 return mark_safe(backend_icon(obj))
274 backend_status_icon.short_description = ""
276 class ReservationInline(PlStackTabularInline):
279 suit_classes = 'suit-tab suit-tab-reservations'
281 def queryset(self, request):
282 return Reservation.select_by_user(request.user)
284 class TagInline(PlStackGenericTabularInline):
287 suit_classes = 'suit-tab suit-tab-tags'
288 fields = ['service', 'name', 'value']
290 def queryset(self, request):
291 return Tag.select_by_user(request.user)
293 class NetworkLookerUpper:
294 """ This is a callable that looks up a network name in a sliver and returns
295 the ip address for that network.
298 byNetworkName = {} # class variable
300 def __init__(self, name):
301 self.short_description = name
303 self.network_name = name
305 def __call__(self, obj):
307 for nbs in obj.networksliver_set.all():
308 if (nbs.network.name == self.network_name):
313 return self.network_name
316 def get(network_name):
317 """ We want to make sure we alwars return the same NetworkLookerUpper
318 because sometimes django will cause them to be instantiated multiple
319 times (and we don't want different ones in form.fields vs
320 SliverInline.readonly_fields).
322 if network_name not in NetworkLookerUpper.byNetworkName:
323 NetworkLookerUpper.byNetworkName[network_name] = NetworkLookerUpper(network_name)
324 return NetworkLookerUpper.byNetworkName[network_name]
326 class SliverInline(PlStackTabularInline):
328 fields = ['backend_status_icon', 'all_ips_string', 'instance_name', 'slice', 'deploymentNetwork', 'flavor', 'image', 'node']
330 readonly_fields = ['backend_status_icon', 'all_ips_string', 'instance_name']
331 suit_classes = 'suit-tab suit-tab-slivers'
333 def queryset(self, request):
334 return Sliver.select_by_user(request.user)
336 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
337 if db_field.name == 'deploymentNetwork':
338 kwargs['queryset'] = Deployment.select_by_acl(request.user)
339 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_deployment_changed(this);"})
340 elif db_field.name == 'flavor':
341 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_flavor_changed(this);"})
343 field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
347 class SiteInline(PlStackTabularInline):
350 suit_classes = 'suit-tab suit-tab-sites'
352 def queryset(self, request):
353 return Site.select_by_user(request.user)
355 class UserInline(PlStackTabularInline):
357 fields = ['backend_status_icon', 'email', 'firstname', 'lastname']
358 readonly_fields = ('backend_status_icon', )
360 suit_classes = 'suit-tab suit-tab-users'
362 def queryset(self, request):
363 return User.select_by_user(request.user)
365 class SliceInline(PlStackTabularInline):
367 fields = ['backend_status_icon', 'name', 'site', 'serviceClass', 'service']
368 readonly_fields = ('backend_status_icon', )
370 suit_classes = 'suit-tab suit-tab-slices'
372 def queryset(self, request):
373 return Slice.select_by_user(request.user)
375 class NodeInline(PlStackTabularInline):
378 suit_classes = 'suit-tab suit-tab-nodes'
379 fields = ['backend_status_icon', 'name','deployment','site']
380 readonly_fields = ('backend_status_icon', )
382 class DeploymentPrivilegeInline(PlStackTabularInline):
383 model = DeploymentPrivilege
385 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
386 fields = ['backend_status_icon', 'user','role','deployment']
387 readonly_fields = ('backend_status_icon', )
389 def queryset(self, request):
390 return DeploymentPrivilege.select_by_user(request.user)
392 class SitePrivilegeInline(PlStackTabularInline):
393 model = SitePrivilege
395 suit_classes = 'suit-tab suit-tab-siteprivileges'
396 fields = ['backend_status_icon', 'user','site', 'role']
397 readonly_fields = ('backend_status_icon', )
399 def formfield_for_foreignkey(self, db_field, request, **kwargs):
400 if db_field.name == 'site':
401 kwargs['queryset'] = Site.select_by_user(request.user)
403 if db_field.name == 'user':
404 kwargs['queryset'] = User.select_by_user(request.user)
405 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
407 def queryset(self, request):
408 return SitePrivilege.select_by_user(request.user)
410 class SiteDeploymentInline(PlStackTabularInline):
411 model = SiteDeployments
413 suit_classes = 'suit-tab suit-tab-deployments'
414 fields = ['backend_status_icon', 'deployment','site']
415 readonly_fields = ('backend_status_icon', )
417 def formfield_for_foreignkey(self, db_field, request, **kwargs):
418 if db_field.name == 'site':
419 kwargs['queryset'] = Site.select_by_user(request.user)
421 if db_field.name == 'deployment':
422 kwargs['queryset'] = Deployment.select_by_user(request.user)
423 return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
425 def queryset(self, request):
426 return SiteDeployments.select_by_user(request.user)
429 class SlicePrivilegeInline(PlStackTabularInline):
430 model = SlicePrivilege
431 suit_classes = 'suit-tab suit-tab-sliceprivileges'
433 fields = ('backend_status_icon', 'user', 'slice', 'role')
434 readonly_fields = ('backend_status_icon', )
436 def formfield_for_foreignkey(self, db_field, request, **kwargs):
437 if db_field.name == 'slice':
438 kwargs['queryset'] = Slice.select_by_user(request.user)
439 if db_field.name == 'user':
440 kwargs['queryset'] = User.select_by_user(request.user)
442 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
444 def queryset(self, request):
445 return SlicePrivilege.select_by_user(request.user)
447 class SliceNetworkInline(PlStackTabularInline):
448 model = Network.slices.through
449 selflink_fieldname = "network"
451 verbose_name = "Network Connection"
452 verbose_name_plural = "Network Connections"
453 suit_classes = 'suit-tab suit-tab-slicenetworks'
454 fields = ['backend_status_icon', 'network']
455 readonly_fields = ('backend_status_icon', )
457 class ImageDeploymentsInline(PlStackTabularInline):
458 model = ImageDeployments
460 verbose_name = "Image Deployments"
461 verbose_name_plural = "Image Deployments"
462 suit_classes = 'suit-tab suit-tab-imagedeployments'
463 fields = ['backend_status_icon', 'image', 'deployment', 'glance_image_id']
464 readonly_fields = ['backend_status_icon', 'glance_image_id']
466 class SliceRoleAdmin(PlanetStackBaseAdmin):
470 class SiteRoleAdmin(PlanetStackBaseAdmin):
474 class DeploymentAdminForm(forms.ModelForm):
475 sites = forms.ModelMultipleChoiceField(
476 queryset=Site.objects.all(),
478 help_text="Select which sites are allowed to host nodes in this deployment",
479 widget=FilteredSelectMultiple(
480 verbose_name=('Sites'), is_stacked=False
483 images = forms.ModelMultipleChoiceField(
484 queryset=Image.objects.all(),
486 help_text="Select which images should be deployed on this deployment",
487 widget=FilteredSelectMultiple(
488 verbose_name=('Images'), is_stacked=False
491 flavors = forms.ModelMultipleChoiceField(
492 queryset=Flavor.objects.all(),
494 help_text="Select which flavors should be usable on this deployment",
495 widget=FilteredSelectMultiple(
496 verbose_name=('Flavors'), is_stacked=False
501 many_to_many = ["flavors",]
503 def __init__(self, *args, **kwargs):
504 request = kwargs.pop('request', None)
505 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
507 self.fields['accessControl'].initial = "allow site " + request.user.site.name
509 if self.instance and self.instance.pk:
510 self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments_set.all()]
511 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments_set.all()]
512 self.fields['flavors'].initial = self.instance.flavors.all()
514 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
515 """ helper function for handling m2m relations from the MultipleChoiceField
517 this_obj: the source object we want to link from
519 selected_objs: a list of destination objects we want to link to
521 all_relations: the full set of relations involving this_obj, including ones we don't want
523 relation_class: the class that implements the relation from source to dest
525 local_attrname: field name representing this_obj in relation_class
527 foreign_attrname: field name representing selected_objs in relation_class
529 This function will remove all newobjclass relations from this_obj
530 that are not contained in selected_objs, and add any relations that
531 are in selected_objs but don't exist in the data model yet.
534 existing_dest_objs = []
535 for relation in list(all_relations):
536 if getattr(relation, foreign_attrname) not in selected_objs:
537 #print "deleting site", sdp.site
540 existing_dest_objs.append(getattr(relation, foreign_attrname))
542 for dest_obj in selected_objs:
543 if dest_obj not in existing_dest_objs:
544 #print "adding site", site
545 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
546 relation = relation_class(**kwargs)
549 def save(self, commit=True):
550 deployment = super(DeploymentAdminForm, self).save(commit=False)
554 # this has to be done after save() if/when a deployment is first created
555 deployment.flavors = self.cleaned_data['flavors']
558 # save_m2m() doesn't seem to work with 'through' relations. So we
559 # create/destroy the through models ourselves. There has to be
562 self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments_set.all(), SiteDeployments, "deployment", "site")
563 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments_set.all(), ImageDeployments, "deployment", "image")
569 class DeploymentAdminROForm(DeploymentAdminForm):
570 def save(self, commit=True):
571 raise PermissionDenied
573 class SiteAssocInline(PlStackTabularInline):
574 model = Site.deployments.through
576 suit_classes = 'suit-tab suit-tab-sites'
578 class DeploymentAdmin(PlanetStackBaseAdmin):
580 fieldList = ['backend_status_text', 'name', 'availability_zone', 'sites', 'images', 'flavors', 'accessControl']
581 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
582 inlines = [DeploymentPrivilegeInline,NodeInline,TagInline] # ,ImageDeploymentsInline]
583 list_display = ['backend_status_icon', 'name']
584 list_display_links = ('backend_status_icon', 'name', )
585 readonly_fields = ('backend_status_text', )
587 user_readonly_fields = ['name']
589 suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags')) # ,('imagedeployments','Images'))
591 def get_form(self, request, obj=None, **kwargs):
592 if request.user.isReadOnlyUser():
593 kwargs["form"] = DeploymentAdminROForm
595 kwargs["form"] = DeploymentAdminForm
596 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
598 # from stackexchange: pass the request object into the form
600 class AdminFormMetaClass(adminForm):
601 def __new__(cls, *args, **kwargs):
602 kwargs['request'] = request
603 return adminForm(*args, **kwargs)
605 return AdminFormMetaClass
607 class ServiceAttrAsTabInline(PlStackTabularInline):
608 model = ServiceAttribute
609 fields = ['name','value']
611 suit_classes = 'suit-tab suit-tab-serviceattrs'
613 class ServiceAdmin(PlanetStackBaseAdmin):
614 list_display = ("backend_status_icon","name","description","versionNumber","enabled","published")
615 list_display_links = ('backend_status_icon', 'name', )
616 fieldList = ["backend_status_text","name","description","versionNumber","enabled","published"]
617 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
618 inlines = [ServiceAttrAsTabInline,SliceInline]
619 readonly_fields = ('backend_status_text', )
621 user_readonly_fields = fieldList
623 suit_form_tabs =(('general', 'Service Details'),
625 ('serviceattrs','Additional Attributes'),
628 class SiteAdmin(PlanetStackBaseAdmin):
629 fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
631 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
632 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
634 suit_form_tabs =(('general', 'Site Details'),
636 ('siteprivileges','Privileges'),
637 ('deployments','Deployments'),
642 readonly_fields = ['backend_status_text', 'accountLink']
644 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
646 list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
647 list_display_links = ('backend_status_icon', 'name', )
648 filter_horizontal = ('deployments',)
649 inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentInline]
650 search_fields = ['name']
652 def queryset(self, request):
653 return Site.select_by_user(request.user)
655 def get_formsets(self, request, obj=None):
656 for inline in self.get_inline_instances(request, obj):
657 # hide MyInline in the add view
660 if isinstance(inline, SliverInline):
661 inline.model.caller = request.user
662 yield inline.get_formset(request, obj)
664 def accountLink(self, obj):
665 link_obj = obj.accounts.all()
667 reverse_path = "admin:core_account_change"
668 url = reverse(reverse_path, args =(link_obj[0].id,))
669 return "<a href='%s'>%s</a>" % (url, "view billing details")
671 return "no billing data for this site"
672 accountLink.allow_tags = True
673 accountLink.short_description = "Billing"
675 def save_model(self, request, obj, form, change):
676 # update openstack connection to use this site/tenant
677 obj.save_by_user(request.user)
679 def delete_model(self, request, obj):
680 obj.delete_by_user(request.user)
683 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
684 fieldList = ['backend_status_text', 'user', 'site', 'role']
686 (None, {'fields': fieldList, 'classes':['collapse']})
688 readonly_fields = ('backend_status_text', )
689 list_display = ('backend_status_icon', 'user', 'site', 'role')
690 list_display_links = list_display
691 user_readonly_fields = fieldList
692 user_readonly_inlines = []
694 def formfield_for_foreignkey(self, db_field, request, **kwargs):
695 if db_field.name == 'site':
696 if not request.user.is_admin:
697 # only show sites where user is an admin or pi
699 for site_privilege in SitePrivilege.objects.filer(user=request.user):
700 if site_privilege.role.role_type in ['admin', 'pi']:
701 sites.add(site_privilege.site)
702 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
704 if db_field.name == 'user':
705 if not request.user.is_admin:
706 # only show users from sites where caller has admin or pi role
707 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
708 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
709 sites = [site_privilege.site for site_privilege in site_privileges]
710 site_privileges = SitePrivilege.objects.filter(site__in=sites)
711 emails = [site_privilege.user.email for site_privilege in site_privileges]
712 users = User.objects.filter(email__in=emails)
713 kwargs['queryset'] = users
715 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
717 def queryset(self, request):
718 # admins can see all privileges. Users can only see privileges at sites
719 # where they have the admin role or pi role.
720 qs = super(SitePrivilegeAdmin, self).queryset(request)
721 #if not request.user.is_admin:
722 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
723 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
724 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
725 # sites = Site.objects.filter(login_base__in=login_bases)
726 # qs = qs.filter(site__in=sites)
729 class SliceForm(forms.ModelForm):
733 'service': LinkedSelect
737 cleaned_data = super(SliceForm, self).clean()
738 name = cleaned_data.get('name')
739 site = cleaned_data.get('site')
740 if (not isinstance(site,Site)):
741 # previous code indicates 'site' could be a site_id and not a site?
742 site = Slice.objects.get(id=site.id)
743 if not name.startswith(site.login_base):
744 raise forms.ValidationError('slice name must begin with %s' % site.login_base)
747 class SliceDeploymentsInline(PlStackTabularInline):
748 model = SliceDeployments
750 verbose_name = "Slice Deployment"
751 verbose_name_plural = "Slice Deployments"
752 suit_classes = 'suit-tab suit-tab-admin-only'
753 fields = ['backend_status_icon', 'deployment', 'tenant_id']
754 readonly_fields = ('backend_status_icon', )
756 class SliceAdmin(PlanetStackBaseAdmin):
758 fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
759 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
760 readonly_fields = ('backend_status_text', )
761 list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_slivers')
762 list_display_links = ('backend_status_icon', 'name', )
763 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
764 admin_inlines = [SliceDeploymentsInline]
766 user_readonly_fields = fieldList
768 # suit_form_tabs =(('general', 'Slice Details'),
769 # ('slicenetworks','Networks'),
770 # ('sliceprivileges','Privileges'),
771 # ('slivers','Slivers'),
773 # ('reservations','Reservations'),
777 def suit_form_tabs(self):
778 tabs =[('general', 'Slice Details'),
779 ('slicenetworks','Networks'),
780 ('sliceprivileges','Privileges'),
781 ('slivers','Slivers'),
783 ('reservations','Reservations'),
786 request=getattr(_thread_locals, "request", None)
787 if request and request.user.is_admin:
788 tabs.append( ('admin-only', 'Admin-Only') )
792 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
793 deployment_nodes = []
794 for node in Node.objects.all():
795 deployment_nodes.append( (node.deployment.id, node.id, node.name) )
797 deployment_flavors = []
798 for flavor in Flavor.objects.all():
799 for deployment in flavor.deployments.all():
800 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
802 deployment_images = []
803 for image in Image.objects.all():
804 for imageDeployment in image.imagedeployments_set.all():
805 deployment_images.append( (imageDeployment.deployment.id, image.id, image.name) )
807 site_login_bases = []
808 for site in Site.objects.all():
809 site_login_bases.append((site.id, site.login_base))
811 context["deployment_nodes"] = deployment_nodes
812 context["deployment_flavors"] = deployment_flavors
813 context["deployment_images"] = deployment_images
814 context["site_login_bases"] = site_login_bases
815 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
817 def formfield_for_foreignkey(self, db_field, request, **kwargs):
818 if db_field.name == 'site':
819 kwargs['queryset'] = Site.select_by_user(request.user)
820 kwargs['widget'] = forms.Select(attrs={'onChange': "update_slice_prefix(this, $($(this).closest('fieldset')[0]).find('.field-name input')[0].id)"})
822 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
824 def queryset(self, request):
825 # admins can see all keys. Users can only see slices they belong to.
826 return Slice.select_by_user(request.user)
828 def get_formsets(self, request, obj=None):
829 for inline in self.get_inline_instances(request, obj):
830 # hide MyInline in the add view
833 if isinstance(inline, SliverInline):
834 inline.model.caller = request.user
835 yield inline.get_formset(request, obj)
837 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
839 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
841 readonly_fields = ('backend_status_text', )
842 list_display = ('backend_status_icon', 'user', 'slice', 'role')
843 list_display_links = list_display
845 user_readonly_fields = ['user', 'slice', 'role']
846 user_readonly_inlines = []
848 def formfield_for_foreignkey(self, db_field, request, **kwargs):
849 if db_field.name == 'slice':
850 kwargs['queryset'] = Slice.select_by_user(request.user)
852 if db_field.name == 'user':
853 kwargs['queryset'] = User.select_by_user(request.user)
855 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
857 def queryset(self, request):
858 # admins can see all memberships. Users can only see memberships of
859 # slices where they have the admin role.
860 return SlicePrivilege.select_by_user(request.user)
862 def save_model(self, request, obj, form, change):
863 # update openstack connection to use this site/tenant
864 auth = request.session.get('auth', {})
865 auth['tenant'] = obj.slice.slicename
866 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
869 def delete_model(self, request, obj):
870 # update openstack connection to use this site/tenant
871 auth = request.session.get('auth', {})
872 auth['tenant'] = obj.slice.slicename
873 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
877 class ImageAdmin(PlanetStackBaseAdmin):
879 fieldsets = [('Image Details',
880 {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
881 'classes': ['suit-tab suit-tab-general']})
883 readonly_fields = ('backend_status_text', )
885 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
887 inlines = [SliverInline, ImageDeploymentsInline]
889 user_readonly_fields = ['name', 'disk_format', 'container_format']
891 list_display = ['backend_status_icon', 'name']
892 list_display_links = ('backend_status_icon', 'name', )
894 class NodeForm(forms.ModelForm):
897 'site': LinkedSelect,
898 'deployment': LinkedSelect
901 class NodeAdmin(PlanetStackBaseAdmin):
903 list_display = ('backend_status_icon', 'name', 'site', 'deployment')
904 list_display_links = ('backend_status_icon', 'name', )
905 list_filter = ('deployment',)
907 inlines = [TagInline,SliverInline]
908 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
909 readonly_fields = ('backend_status_text', )
911 user_readonly_fields = ['name','site','deployment']
912 user_readonly_inlines = [TagInline,SliverInline]
914 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
917 class SliverForm(forms.ModelForm):
920 ip = forms.CharField(widget=PlainTextWidget)
921 instance_name = forms.CharField(widget=PlainTextWidget)
923 'ip': PlainTextWidget(),
924 'instance_name': PlainTextWidget(),
925 'slice': LinkedSelect,
926 'deploymentNetwork': LinkedSelect,
927 'node': LinkedSelect,
928 'image': LinkedSelect
931 class TagAdmin(PlanetStackBaseAdmin):
932 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
933 list_display_links = list_display
934 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
935 user_readonly_inlines = []
937 class SliverAdmin(PlanetStackBaseAdmin):
940 ('Sliver Details', {'fields': ['backend_status_text', 'slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
942 readonly_fields = ('backend_status_text', )
943 list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'flavor', 'image', 'node', 'deploymentNetwork']
944 list_display_links = ('backend_status_icon', 'ip',)
946 suit_form_tabs =(('general', 'Sliver Details'),
950 inlines = [TagInline]
952 user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image']
954 def formfield_for_foreignkey(self, db_field, request, **kwargs):
955 if db_field.name == 'slice':
956 kwargs['queryset'] = Slice.select_by_user(request.user)
958 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
960 def queryset(self, request):
961 # admins can see all slivers. Users can only see slivers of
962 # the slices they belong to.
963 return Sliver.select_by_user(request.user)
966 def get_formsets(self, request, obj=None):
967 # make some fields read only if we are updating an existing record
969 #self.readonly_fields = ('ip', 'instance_name')
970 self.readonly_fields = ('backend_status_text')
972 self.readonly_fields = ('backend_status_text')
973 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
975 for inline in self.get_inline_instances(request, obj):
976 # hide MyInline in the add view
979 if isinstance(inline, SliverInline):
980 inline.model.caller = request.user
981 yield inline.get_formset(request, obj)
983 #def save_model(self, request, obj, form, change):
984 # # update openstack connection to use this site/tenant
985 # auth = request.session.get('auth', {})
986 # auth['tenant'] = obj.slice.name
987 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
988 # obj.creator = request.user
991 #def delete_model(self, request, obj):
992 # # update openstack connection to use this site/tenant
993 # auth = request.session.get('auth', {})
994 # auth['tenant'] = obj.slice.name
995 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
998 class UserCreationForm(forms.ModelForm):
999 """A form for creating new users. Includes all the required
1000 fields, plus a repeated password."""
1001 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
1002 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
1006 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
1008 def clean_password2(self):
1009 # Check that the two password entries match
1010 password1 = self.cleaned_data.get("password1")
1011 password2 = self.cleaned_data.get("password2")
1012 if password1 and password2 and password1 != password2:
1013 raise forms.ValidationError("Passwords don't match")
1016 def save(self, commit=True):
1017 # Save the provided password in hashed format
1018 user = super(UserCreationForm, self).save(commit=False)
1019 user.password = self.cleaned_data["password1"]
1020 #user.set_password(self.cleaned_data["password1"])
1026 class UserChangeForm(forms.ModelForm):
1027 """A form for updating users. Includes all the fields on
1028 the user, but replaces the password field with admin's
1029 password hash display field.
1031 password = ReadOnlyPasswordHashField(label='Password',
1032 help_text= '<a href=\"password/\">Change Password</a>.')
1037 def clean_password(self):
1038 # Regardless of what the user provides, return the initial value.
1039 # This is done here, rather than on the field, because the
1040 # field does not have access to the initial value
1041 return self.initial["password"]
1043 class UserDashboardViewInline(PlStackTabularInline):
1044 model = UserDashboardView
1046 suit_classes = 'suit-tab suit-tab-dashboards'
1047 fields = ['user', 'dashboardView', 'order']
1049 class UserAdmin(PermissionCheckingAdminMixin, UserAdmin):
1050 # Note: Make sure PermissionCheckingAdminMixin is listed before
1051 # admin.ModelAdmin in the class declaration.
1056 # The forms to add and change user instances
1057 form = UserChangeForm
1058 add_form = UserCreationForm
1060 # The fields to be used in displaying the User model.
1061 # These override the definitions on the base UserAdmin
1062 # that reference specific fields on auth.User.
1063 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
1064 list_filter = ('site',)
1065 inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
1067 fieldListLoginDetails = ['backend_status_text', 'email','site','password','is_active','is_readonly','is_admin','public_key']
1068 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1071 ('Login Details', {'fields': ['backend_status_text', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1072 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1073 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1074 #('Important dates', {'fields': ('last_login',)}),
1078 'classes': ('wide',),
1079 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')},
1082 readonly_fields = ('backend_status_text', )
1083 search_fields = ('email',)
1084 ordering = ('email',)
1085 filter_horizontal = ()
1087 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1090 def suit_form_tabs(self):
1091 if getattr(_thread_locals, "obj", None) is None:
1094 return (('general','Login Details'),
1095 ('contact','Contact Information'),
1096 ('sliceprivileges','Slice Privileges'),
1097 ('siteprivileges','Site Privileges'),
1098 ('deploymentprivileges','Deployment Privileges'),
1099 ('dashboards','Dashboard Views'))
1101 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1102 if db_field.name == 'site':
1103 kwargs['queryset'] = Site.select_by_user(request.user)
1105 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1107 def queryset(self, request):
1108 return User.select_by_user(request.user)
1110 class DashboardViewAdmin(PlanetStackBaseAdmin):
1111 fieldsets = [('Dashboard View Details',
1112 {'fields': ['backend_status_text', 'name', 'url'],
1113 'classes': ['suit-tab suit-tab-general']})
1115 readonly_fields = ('backend_status_text', )
1117 suit_form_tabs =(('general','Dashboard View Details'),)
1119 class ServiceResourceInline(PlStackTabularInline):
1120 model = ServiceResource
1123 class ServiceClassAdmin(PlanetStackBaseAdmin):
1124 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1125 list_display_links = ('backend_status_icon', 'name', )
1126 inlines = [ServiceResourceInline]
1128 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1129 user_readonly_inlines = []
1131 class ReservedResourceInline(PlStackTabularInline):
1132 model = ReservedResource
1134 suit_classes = 'suit-tab suit-tab-reservedresources'
1136 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1137 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1139 if db_field.name == 'resource':
1140 # restrict resources to those that the slice's service class allows
1141 if request._slice is not None:
1142 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1143 if len(field.queryset) > 0:
1144 field.initial = field.queryset.all()[0]
1146 field.queryset = field.queryset.none()
\r
1147 elif db_field.name == 'sliver':
\r
1148 # restrict slivers to those that belong to the slice
\r
1149 if request._slice is not None:
\r
1150 field.queryset = field.queryset.filter(slice = request._slice)
1152 field.queryset = field.queryset.none()
\r
1156 def queryset(self, request):
1157 return ReservedResource.select_by_user(request.user)
1159 class ReservationChangeForm(forms.ModelForm):
1163 'slice' : LinkedSelect
1166 class ReservationAddForm(forms.ModelForm):
1167 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1168 refresh = forms.CharField(widget=forms.HiddenInput())
1171 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1173 def clean_slice(self):
1174 slice = self.cleaned_data.get("slice")
1175 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1177 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1183 'slice' : LinkedSelect
1187 class ReservationAddRefreshForm(ReservationAddForm):
1188 """ This form is displayed when the Reservation Form receives an update
1189 from the Slice dropdown onChange handler. It doesn't validate the
1190 data and doesn't save the data. This will cause the form to be
1194 """ don't validate anything other than slice """
1195 dont_validate_fields = ("startTime", "duration")
1197 def full_clean(self):
1198 result = super(ReservationAddForm, self).full_clean()
1200 for fieldname in self.dont_validate_fields:
1201 if fieldname in self._errors:
1202 del self._errors[fieldname]
1206 """ don't save anything """
1210 class ReservationAdmin(PlanetStackBaseAdmin):
1211 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
1212 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1213 readonly_fields = ('backend_status_text', )
1214 list_display = ('startTime', 'duration')
1215 form = ReservationAddForm
1217 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1219 inlines = [ReservedResourceInline]
1220 user_readonly_fields = fieldList
1222 def add_view(self, request, form_url='', extra_context=None):
1223 timezone.activate(request.user.timezone)
1224 request._refresh = False
1225 request._slice = None
1226 if request.method == 'POST':
1227 # "refresh" will be set to "1" if the form was submitted due to
1228 # a change in the Slice dropdown.
1229 if request.POST.get("refresh","1") == "1":
1230 request._refresh = True
1231 request.POST["refresh"] = "0"
1233 # Keep track of the slice that was selected, so the
1234 # reservedResource inline can filter items for the slice.
1235 request._slice = request.POST.get("slice",None)
1236 if (request._slice is not None):
1237 request._slice = Slice.objects.get(id=request._slice)
1239 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1242 def changelist_view(self, request, extra_context = None):
1243 timezone.activate(request.user.timezone)
1244 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1246 def get_form(self, request, obj=None, **kwargs):
1249 # For changes, set request._slice to the slice already set in the
1251 request._slice = obj.slice
1252 self.form = ReservationChangeForm
1254 if getattr(request, "_refresh", False):
1255 self.form = ReservationAddRefreshForm
1257 self.form = ReservationAddForm
1258 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1260 def get_readonly_fields(self, request, obj=None):
1261 if (obj is not None):
1262 # Prevent slice from being changed after the reservation has been
1268 def queryset(self, request):
1269 return Reservation.select_by_user(request.user)
1271 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1272 list_display = ("backend_status_icon", "name", )
1273 list_display_links = ('backend_status_icon', 'name', )
1274 user_readonly_fields = ['name']
1275 user_readonly_inlines = []
1277 class RouterAdmin(PlanetStackBaseAdmin):
1278 list_display = ("backend_status_icon", "name", )
1279 list_display_links = ('backend_status_icon', 'name', )
1280 user_readonly_fields = ['name']
1281 user_readonly_inlines = []
1283 class RouterInline(PlStackTabularInline):
1284 model = Router.networks.through
1286 verbose_name_plural = "Routers"
1287 verbose_name = "Router"
1288 suit_classes = 'suit-tab suit-tab-routers'
1290 class NetworkParameterInline(PlStackGenericTabularInline):
1291 model = NetworkParameter
1293 verbose_name_plural = "Parameters"
1294 verbose_name = "Parameter"
1295 suit_classes = 'suit-tab suit-tab-netparams'
1296 fields = ['backend_status_icon', 'parameter', 'value']
1297 readonly_fields = ('backend_status_icon', )
1299 class NetworkSliversInline(PlStackTabularInline):
1300 fields = ['backend_status_icon', 'network','sliver','ip']
1301 readonly_fields = ("backend_status_icon", "ip", )
1302 model = NetworkSliver
1303 selflink_fieldname = "sliver"
1305 verbose_name_plural = "Slivers"
1306 verbose_name = "Sliver"
1307 suit_classes = 'suit-tab suit-tab-networkslivers'
1309 class NetworkSlicesInline(PlStackTabularInline):
1310 model = NetworkSlice
1311 selflink_fieldname = "slice"
1313 verbose_name_plural = "Slices"
1314 verbose_name = "Slice"
1315 suit_classes = 'suit-tab suit-tab-networkslices'
1316 fields = ['backend_status_icon', 'network','slice']
1317 readonly_fields = ('backend_status_icon', )
1319 class NetworkAdmin(PlanetStackBaseAdmin):
1320 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1321 list_display_links = ('backend_status_icon', 'name', )
1322 readonly_fields = ("subnet", )
1324 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1327 (None, {'fields': ['backend_status_text', 'name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet'], 'classes':['suit-tab suit-tab-general']}),]
1329 readonly_fields = ('backend_status_text', )
1330 user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1333 ('general','Network Details'),
1334 ('netparams', 'Parameters'),
1335 ('networkslivers','Slivers'),
1336 ('networkslices','Slices'),
1337 ('routers','Routers'),
1339 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1340 list_display = ("backend_status_icon", "name", "guaranteedBandwidth", "visibility")
1341 list_display_links = ('backend_status_icon', 'name', )
1342 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1343 user_readonly_inlines = []
1345 class FlavorAdmin(PlanetStackBaseAdmin):
1346 list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1347 list_display_links = ("backend_status_icon", "name")
1348 user_readonly_fields = ("name", "flavor")
1349 fields = ("name", "description", "flavor", "order", "default")
1351 # register a signal that caches the user's credentials when they log in
1352 def cache_credentials(sender, user, request, **kwds):
1353 auth = {'username': request.POST['username'],
1354 'password': request.POST['password']}
1355 request.session['auth'] = auth
1356 user_logged_in.connect(cache_credentials)
1358 def dollar_field(fieldName, short_description):
1359 def newFunc(self, obj):
1361 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1363 x=getattr(obj, fieldName, 0.0)
1365 newFunc.short_description = short_description
1368 def right_dollar_field(fieldName, short_description):
1369 def newFunc(self, obj):
1371 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1372 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1374 x=getattr(obj, fieldName, 0.0)
1376 newFunc.short_description = short_description
1377 newFunc.allow_tags = True
1380 class InvoiceChargeInline(PlStackTabularInline):
1383 verbose_name_plural = "Charges"
1384 verbose_name = "Charge"
1385 exclude = ['account']
1386 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1387 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1391 dollar_amount = right_dollar_field("amount", "Amount")
1393 class InvoiceAdmin(admin.ModelAdmin):
1394 list_display = ("date", "account")
1396 inlines = [InvoiceChargeInline]
1398 fields = ["date", "account", "dollar_amount"]
1399 readonly_fields = ["date", "account", "dollar_amount"]
1401 dollar_amount = dollar_field("amount", "Amount")
1403 class InvoiceInline(PlStackTabularInline):
1406 verbose_name_plural = "Invoices"
1407 verbose_name = "Invoice"
1408 fields = ["date", "dollar_amount"]
1409 readonly_fields = ["date", "dollar_amount"]
1410 suit_classes = 'suit-tab suit-tab-accountinvoice'
1414 dollar_amount = right_dollar_field("amount", "Amount")
1416 class PendingChargeInline(PlStackTabularInline):
1419 verbose_name_plural = "Charges"
1420 verbose_name = "Charge"
1421 exclude = ["invoice"]
1422 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1423 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1424 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1428 def queryset(self, request):
1429 qs = super(PendingChargeInline, self).queryset(request)
1430 qs = qs.filter(state="pending")
1433 dollar_amount = right_dollar_field("amount", "Amount")
1435 class PaymentInline(PlStackTabularInline):
1438 verbose_name_plural = "Payments"
1439 verbose_name = "Payment"
1440 fields = ["date", "dollar_amount"]
1441 readonly_fields = ["date", "dollar_amount"]
1442 suit_classes = 'suit-tab suit-tab-accountpayments'
1446 dollar_amount = right_dollar_field("amount", "Amount")
1448 class AccountAdmin(admin.ModelAdmin):
1449 list_display = ("site", "balance_due")
1451 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1454 (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1456 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1459 ('general','Account Details'),
1460 ('accountinvoice', 'Invoices'),
1461 ('accountpayments', 'Payments'),
1462 ('accountpendingcharges','Pending Charges'),
1465 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1466 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1467 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1469 # Now register the new UserAdmin...
1470 admin.site.register(User, UserAdmin)
1471 # ... and, since we're not using Django's builtin permissions,
1472 # unregister the Group model from admin.
1473 #admin.site.unregister(Group)
1475 #Do not show django evolution in the admin interface
1476 from django_evolution.models import Version, Evolution
1477 #admin.site.unregister(Version)
1478 #admin.site.unregister(Evolution)
1481 # When debugging it is often easier to see all the classes, but for regular use
1482 # only the top-levels should be displayed
1485 admin.site.register(Deployment, DeploymentAdmin)
1486 admin.site.register(Site, SiteAdmin)
1487 admin.site.register(Slice, SliceAdmin)
1488 admin.site.register(Service, ServiceAdmin)
1489 admin.site.register(Reservation, ReservationAdmin)
1490 admin.site.register(Network, NetworkAdmin)
1491 admin.site.register(Router, RouterAdmin)
1492 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1493 admin.site.register(Account, AccountAdmin)
1494 admin.site.register(Invoice, InvoiceAdmin)
1497 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1498 admin.site.register(ServiceClass, ServiceClassAdmin)
1499 #admin.site.register(PlanetStack)
1500 admin.site.register(Tag, TagAdmin)
1501 admin.site.register(DeploymentRole)
1502 admin.site.register(SiteRole)
1503 admin.site.register(SliceRole)
1504 admin.site.register(PlanetStackRole)
1505 admin.site.register(Node, NodeAdmin)
1506 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1507 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1508 admin.site.register(Sliver, SliverAdmin)
1509 admin.site.register(Image, ImageAdmin)
1510 admin.site.register(DashboardView, DashboardViewAdmin)
1511 admin.site.register(Flavor, FlavorAdmin)