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 class ReadOnlyAwareAdmin(PermissionCheckingAdminMixin, admin.ModelAdmin):
141 # Note: Make sure PermissionCheckingAdminMixin is listed before
142 # admin.ModelAdmin in the class declaration.
146 class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
149 class SingletonAdmin (ReadOnlyAwareAdmin):
150 def has_add_permission(self, request):
151 if not super(SingletonAdmin, self).has_add_permission(request):
154 num_objects = self.model.objects.count()
160 class PlStackTabularInline(admin.TabularInline):
161 def __init__(self, *args, **kwargs):
162 super(PlStackTabularInline, self).__init__(*args, **kwargs)
164 # InlineModelAdmin as no get_fields() method, so in order to add
165 # the selflink field, we override __init__ to modify self.fields and
166 # self.readonly_fields.
168 self.setup_selflink()
170 def get_change_url(self, model, id):
171 """ Get the URL to a change form in the admin for this model """
172 reverse_path = "admin:%s_change" % (model._meta.db_table)
174 url = reverse(reverse_path, args=(id,))
175 except NoReverseMatch:
180 def setup_selflink(self):
181 if hasattr(self, "selflink_fieldname"):
182 """ self.selflink_model can be defined to punch through a relation
183 to its target object. For example, in SliceNetworkInline, set
184 selflink_model = "network", and the URL will lead to the Network
185 object instead of trying to bring up a change view of the
188 self.selflink_model = getattr(self.model,self.selflink_fieldname).field.rel.to
190 self.selflink_model = self.model
192 url = self.get_change_url(self.selflink_model, 0)
194 # We don't have an admin for this object, so don't create the
199 # Since we need to add "selflink" to the field list, we need to create
200 # self.fields if it is None.
201 if (self.fields is None):
203 for f in self.model._meta.fields:
204 if f.editable and f.name != "id":
205 self.fields.append(f.name)
207 self.fields = tuple(self.fields) + ("selflink", )
209 if self.readonly_fields is None:
210 self.readonly_fields = ()
212 self.readonly_fields = tuple(self.readonly_fields) + ("selflink", )
214 def selflink(self, obj):
215 if hasattr(self, "selflink_fieldname"):
216 obj = getattr(obj, self.selflink_fieldname)
219 url = self.get_change_url(self.selflink_model, obj.id)
220 return "<a href='%s'>Details</a>" % str(url)
222 return "Not present"
\r
224 selflink.allow_tags = True
225 selflink.short_description = "Details"
227 def has_add_permission(self, request):
228 return not request.user.isReadOnlyUser()
230 def get_readonly_fields(self, request, obj=None):
231 readonly_fields = list(self.readonly_fields)[:]
232 if request.user.isReadOnlyUser():
233 for field in self.fields:
234 if not field in readonly_fields:
235 readonly_fields.append(field)
236 return readonly_fields
238 def backend_status_icon(self, obj):
239 return mark_safe(backend_icon(obj))
240 backend_status_icon.short_description = ""
242 class PlStackGenericTabularInline(generic.GenericTabularInline):
243 def has_add_permission(self, request):
244 return not request.user.isReadOnlyUser()
246 def get_readonly_fields(self, request, obj=None):
247 readonly_fields = list(self.readonly_fields)[:]
248 if request.user.isReadOnlyUser():
249 for field in self.fields:
250 if not field in readonly_fields:
251 readonly_fields.append(field)
252 return readonly_fields
254 def backend_status_icon(self, obj):
255 return mark_safe(backend_icon(obj))
256 backend_status_icon.short_description = ""
258 class ReservationInline(PlStackTabularInline):
261 suit_classes = 'suit-tab suit-tab-reservations'
263 def queryset(self, request):
264 return Reservation.select_by_user(request.user)
266 class TagInline(PlStackGenericTabularInline):
269 suit_classes = 'suit-tab suit-tab-tags'
270 fields = ['service', 'name', 'value']
272 def queryset(self, request):
273 return Tag.select_by_user(request.user)
275 class NetworkLookerUpper:
276 """ This is a callable that looks up a network name in a sliver and returns
277 the ip address for that network.
280 byNetworkName = {} # class variable
282 def __init__(self, name):
283 self.short_description = name
285 self.network_name = name
287 def __call__(self, obj):
289 for nbs in obj.networksliver_set.all():
290 if (nbs.network.name == self.network_name):
295 return self.network_name
298 def get(network_name):
299 """ We want to make sure we alwars return the same NetworkLookerUpper
300 because sometimes django will cause them to be instantiated multiple
301 times (and we don't want different ones in form.fields vs
302 SliverInline.readonly_fields).
304 if network_name not in NetworkLookerUpper.byNetworkName:
305 NetworkLookerUpper.byNetworkName[network_name] = NetworkLookerUpper(network_name)
306 return NetworkLookerUpper.byNetworkName[network_name]
308 class SliverInline(PlStackTabularInline):
310 fields = ['backend_status_icon', 'all_ips_string', 'instance_name', 'slice', 'deploymentNetwork', 'flavor', 'image', 'node']
312 readonly_fields = ['backend_status_icon', 'all_ips_string', 'instance_name']
313 suit_classes = 'suit-tab suit-tab-slivers'
315 def queryset(self, request):
316 return Sliver.select_by_user(request.user)
318 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
319 if db_field.name == 'deploymentNetwork':
320 kwargs['queryset'] = Deployment.select_by_acl(request.user)
321 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_deployment_changed(this);"})
322 elif db_field.name == 'flavor':
323 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_flavor_changed(this);"})
325 field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
329 class SiteInline(PlStackTabularInline):
332 suit_classes = 'suit-tab suit-tab-sites'
334 def queryset(self, request):
335 return Site.select_by_user(request.user)
337 class UserInline(PlStackTabularInline):
339 fields = ['backend_status_icon', 'email', 'firstname', 'lastname']
340 readonly_fields = ('backend_status_icon', )
342 suit_classes = 'suit-tab suit-tab-users'
344 def queryset(self, request):
345 return User.select_by_user(request.user)
347 class SliceInline(PlStackTabularInline):
349 fields = ['backend_status_icon', 'name', 'site', 'serviceClass', 'service']
350 readonly_fields = ('backend_status_icon', )
352 suit_classes = 'suit-tab suit-tab-slices'
354 def queryset(self, request):
355 return Slice.select_by_user(request.user)
357 class NodeInline(PlStackTabularInline):
360 suit_classes = 'suit-tab suit-tab-nodes'
361 fields = ['backend_status_icon', 'name','deployment','site']
362 readonly_fields = ('backend_status_icon', )
364 class DeploymentPrivilegeInline(PlStackTabularInline):
365 model = DeploymentPrivilege
367 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
368 fields = ['backend_status_icon', 'user','role','deployment']
369 readonly_fields = ('backend_status_icon', )
371 def queryset(self, request):
372 return DeploymentPrivilege.select_by_user(request.user)
374 class SitePrivilegeInline(PlStackTabularInline):
375 model = SitePrivilege
377 suit_classes = 'suit-tab suit-tab-siteprivileges'
378 fields = ['backend_status_icon', 'user','site', 'role']
379 readonly_fields = ('backend_status_icon', )
381 def formfield_for_foreignkey(self, db_field, request, **kwargs):
382 if db_field.name == 'site':
383 kwargs['queryset'] = Site.select_by_user(request.user)
385 if db_field.name == 'user':
386 kwargs['queryset'] = User.select_by_user(request.user)
387 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
389 def queryset(self, request):
390 return SitePrivilege.select_by_user(request.user)
392 class SiteDeploymentInline(PlStackTabularInline):
393 model = SiteDeployments
395 suit_classes = 'suit-tab suit-tab-deployments'
396 fields = ['backend_status_icon', 'deployment','site']
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 == 'deployment':
404 kwargs['queryset'] = Deployment.select_by_user(request.user)
405 return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
407 def queryset(self, request):
408 return SiteDeployments.select_by_user(request.user)
411 class SlicePrivilegeInline(PlStackTabularInline):
412 model = SlicePrivilege
413 suit_classes = 'suit-tab suit-tab-sliceprivileges'
415 fields = ('backend_status_icon', 'user', 'slice', 'role')
416 readonly_fields = ('backend_status_icon', )
418 def formfield_for_foreignkey(self, db_field, request, **kwargs):
419 if db_field.name == 'slice':
420 kwargs['queryset'] = Slice.select_by_user(request.user)
421 if db_field.name == 'user':
422 kwargs['queryset'] = User.select_by_user(request.user)
424 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
426 def queryset(self, request):
427 return SlicePrivilege.select_by_user(request.user)
429 class SliceNetworkInline(PlStackTabularInline):
430 model = Network.slices.through
431 selflink_fieldname = "network"
433 verbose_name = "Network Connection"
434 verbose_name_plural = "Network Connections"
435 suit_classes = 'suit-tab suit-tab-slicenetworks'
436 fields = ['backend_status_icon', 'network']
437 readonly_fields = ('backend_status_icon', )
439 class ImageDeploymentsInline(PlStackTabularInline):
440 model = ImageDeployments
442 verbose_name = "Image Deployments"
443 verbose_name_plural = "Image Deployments"
444 suit_classes = 'suit-tab suit-tab-imagedeployments'
445 fields = ['backend_status_icon', 'image', 'deployment', 'glance_image_id']
446 readonly_fields = ['backend_status_icon', 'glance_image_id']
448 class SliceRoleAdmin(PlanetStackBaseAdmin):
452 class SiteRoleAdmin(PlanetStackBaseAdmin):
456 class DeploymentAdminForm(forms.ModelForm):
457 sites = forms.ModelMultipleChoiceField(
458 queryset=Site.objects.all(),
460 help_text="Select which sites are allowed to host nodes in this deployment",
461 widget=FilteredSelectMultiple(
462 verbose_name=('Sites'), is_stacked=False
465 images = forms.ModelMultipleChoiceField(
466 queryset=Image.objects.all(),
468 help_text="Select which images should be deployed on this deployment",
469 widget=FilteredSelectMultiple(
470 verbose_name=('Images'), is_stacked=False
473 flavors = forms.ModelMultipleChoiceField(
474 queryset=Flavor.objects.all(),
476 help_text="Select which flavors should be usable on this deployment",
477 widget=FilteredSelectMultiple(
478 verbose_name=('Flavors'), is_stacked=False
483 many_to_many = ["flavors",]
485 def __init__(self, *args, **kwargs):
486 request = kwargs.pop('request', None)
487 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
489 self.fields['accessControl'].initial = "allow site " + request.user.site.name
491 if self.instance and self.instance.pk:
492 self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments_set.all()]
493 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments_set.all()]
494 self.fields['flavors'].initial = self.instance.flavors.all()
496 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
497 """ helper function for handling m2m relations from the MultipleChoiceField
499 this_obj: the source object we want to link from
501 selected_objs: a list of destination objects we want to link to
503 all_relations: the full set of relations involving this_obj, including ones we don't want
505 relation_class: the class that implements the relation from source to dest
507 local_attrname: field name representing this_obj in relation_class
509 foreign_attrname: field name representing selected_objs in relation_class
511 This function will remove all newobjclass relations from this_obj
512 that are not contained in selected_objs, and add any relations that
513 are in selected_objs but don't exist in the data model yet.
516 existing_dest_objs = []
517 for relation in list(all_relations):
518 if getattr(relation, foreign_attrname) not in selected_objs:
519 #print "deleting site", sdp.site
522 existing_dest_objs.append(getattr(relation, foreign_attrname))
524 for dest_obj in selected_objs:
525 if dest_obj not in existing_dest_objs:
526 #print "adding site", site
527 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
528 relation = relation_class(**kwargs)
531 def save(self, commit=True):
532 deployment = super(DeploymentAdminForm, self).save(commit=False)
536 # this has to be done after save() if/when a deployment is first created
537 deployment.flavors = self.cleaned_data['flavors']
540 # save_m2m() doesn't seem to work with 'through' relations. So we
541 # create/destroy the through models ourselves. There has to be
544 self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments_set.all(), SiteDeployments, "deployment", "site")
545 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments_set.all(), ImageDeployments, "deployment", "image")
551 class DeploymentAdminROForm(DeploymentAdminForm):
552 def save(self, commit=True):
553 raise PermissionDenied
555 class SiteAssocInline(PlStackTabularInline):
556 model = Site.deployments.through
558 suit_classes = 'suit-tab suit-tab-sites'
560 class DeploymentAdmin(PlanetStackBaseAdmin):
562 fieldList = ['backend_status_text', 'name', 'availability_zone', 'sites', 'images', 'flavors', 'accessControl']
563 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
564 inlines = [DeploymentPrivilegeInline,NodeInline,TagInline] # ,ImageDeploymentsInline]
565 list_display = ['backend_status_icon', 'name']
566 list_display_links = ('backend_status_icon', 'name', )
567 readonly_fields = ('backend_status_text', )
569 user_readonly_fields = ['name']
571 suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags')) # ,('imagedeployments','Images'))
573 def get_form(self, request, obj=None, **kwargs):
574 if request.user.isReadOnlyUser():
575 kwargs["form"] = DeploymentAdminROForm
577 kwargs["form"] = DeploymentAdminForm
578 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
580 # from stackexchange: pass the request object into the form
582 class AdminFormMetaClass(adminForm):
583 def __new__(cls, *args, **kwargs):
584 kwargs['request'] = request
585 return adminForm(*args, **kwargs)
587 return AdminFormMetaClass
589 class ServiceAttrAsTabInline(PlStackTabularInline):
590 model = ServiceAttribute
591 fields = ['name','value']
593 suit_classes = 'suit-tab suit-tab-serviceattrs'
595 class ServiceAdmin(PlanetStackBaseAdmin):
596 list_display = ("backend_status_icon","name","description","versionNumber","enabled","published")
597 list_display_links = ('backend_status_icon', 'name', )
598 fieldList = ["backend_status_text","name","description","versionNumber","enabled","published"]
599 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
600 inlines = [ServiceAttrAsTabInline,SliceInline]
601 readonly_fields = ('backend_status_text', )
603 user_readonly_fields = fieldList
605 suit_form_tabs =(('general', 'Service Details'),
607 ('serviceattrs','Additional Attributes'),
610 class SiteAdmin(PlanetStackBaseAdmin):
611 fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
613 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
614 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
616 suit_form_tabs =(('general', 'Site Details'),
618 ('siteprivileges','Privileges'),
619 ('deployments','Deployments'),
624 readonly_fields = ['backend_status_text', 'accountLink']
626 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
628 list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
629 list_display_links = ('backend_status_icon', 'name', )
630 filter_horizontal = ('deployments',)
631 inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentInline]
632 search_fields = ['name']
634 def queryset(self, request):
635 return Site.select_by_user(request.user)
637 def get_formsets(self, request, obj=None):
638 for inline in self.get_inline_instances(request, obj):
639 # hide MyInline in the add view
642 if isinstance(inline, SliceInline):
643 inline.model.caller = request.user
644 yield inline.get_formset(request, obj)
646 def get_formsets(self, request, obj=None):
647 for inline in self.get_inline_instances(request, obj):
648 # hide MyInline in the add view
651 if isinstance(inline, SliverInline):
652 inline.model.caller = request.user
653 yield inline.get_formset(request, obj)
655 def accountLink(self, obj):
656 link_obj = obj.accounts.all()
658 reverse_path = "admin:core_account_change"
659 url = reverse(reverse_path, args =(link_obj[0].id,))
660 return "<a href='%s'>%s</a>" % (url, "view billing details")
662 return "no billing data for this site"
663 accountLink.allow_tags = True
664 accountLink.short_description = "Billing"
666 def save_model(self, request, obj, form, change):
667 # update openstack connection to use this site/tenant
668 obj.save_by_user(request.user)
670 def delete_model(self, request, obj):
671 obj.delete_by_user(request.user)
674 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
675 fieldList = ['backend_status_text', 'user', 'site', 'role']
677 (None, {'fields': fieldList, 'classes':['collapse']})
679 readonly_fields = ('backend_status_text', )
680 list_display = ('backend_status_icon', 'user', 'site', 'role')
681 list_display_links = list_display
682 user_readonly_fields = fieldList
683 user_readonly_inlines = []
685 def formfield_for_foreignkey(self, db_field, request, **kwargs):
686 if db_field.name == 'site':
687 if not request.user.is_admin:
688 # only show sites where user is an admin or pi
690 for site_privilege in SitePrivilege.objects.filer(user=request.user):
691 if site_privilege.role.role_type in ['admin', 'pi']:
692 sites.add(site_privilege.site)
693 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
695 if db_field.name == 'user':
696 if not request.user.is_admin:
697 # only show users from sites where caller has admin or pi role
698 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
699 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
700 sites = [site_privilege.site for site_privilege in site_privileges]
701 site_privileges = SitePrivilege.objects.filter(site__in=sites)
702 emails = [site_privilege.user.email for site_privilege in site_privileges]
703 users = User.objects.filter(email__in=emails)
704 kwargs['queryset'] = users
706 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
708 def queryset(self, request):
709 # admins can see all privileges. Users can only see privileges at sites
710 # where they have the admin role or pi role.
711 qs = super(SitePrivilegeAdmin, self).queryset(request)
712 #if not request.user.is_admin:
713 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
714 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
715 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
716 # sites = Site.objects.filter(login_base__in=login_bases)
717 # qs = qs.filter(site__in=sites)
720 class SliceForm(forms.ModelForm):
724 'service': LinkedSelect
728 cleaned_data = super(SliceForm, self).clean()
729 name = cleaned_data.get('name')
730 site = cleaned_data.get('site')
731 if (not isinstance(site,Site)):
732 # previous code indicates 'site' could be a site_id and not a site?
733 site = Slice.objects.get(id=site.id)
734 if not name.startswith(site.login_base):
735 raise forms.ValidationError('slice name must begin with %s' % site.login_base)
738 class SliceDeploymentsInline(PlStackTabularInline):
739 model = SliceDeployments
741 verbose_name = "Slice Deployment"
742 verbose_name_plural = "Slice Deployments"
743 suit_classes = 'suit-tab suit-tab-admin-only'
744 fields = ['backend_status_icon', 'deployment', 'tenant_id']
745 readonly_fields = ('backend_status_icon', )
747 class SliceAdmin(PlanetStackBaseAdmin):
749 fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
750 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
751 readonly_fields = ('backend_status_text', )
752 list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_slivers')
753 list_display_links = ('backend_status_icon', 'name', )
754 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
755 admin_inlines = [SliceDeploymentsInline]
757 user_readonly_fields = fieldList
759 # suit_form_tabs =(('general', 'Slice Details'),
760 # ('slicenetworks','Networks'),
761 # ('sliceprivileges','Privileges'),
762 # ('slivers','Slivers'),
764 # ('reservations','Reservations'),
767 def get_form(self, request, obj=None):
768 # Save obj in thread-local storage, so suit_form_tabs can use it to
769 # determine whether we're in edit or add mode.
770 _thread_locals.request = request
771 _thread_locals.obj = obj
772 return super(SliceAdmin, self).get_form(request, obj)
775 def suit_form_tabs(self):
776 tabs =[('general', 'Slice Details'),
777 ('slicenetworks','Networks'),
778 ('sliceprivileges','Privileges'),
779 ('slivers','Slivers'),
781 ('reservations','Reservations'),
784 request=getattr(_thread_locals, "request", None)
785 if request and request.user.is_admin:
786 tabs.append( ('admin-only', 'Admin-Only') )
790 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
791 deployment_nodes = []
792 for node in Node.objects.all():
793 deployment_nodes.append( (node.deployment.id, node.id, node.name) )
795 deployment_flavors = []
796 for flavor in Flavor.objects.all():
797 for deployment in flavor.deployments.all():
798 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
800 deployment_images = []
801 for image in Image.objects.all():
802 for imageDeployment in image.imagedeployments_set.all():
803 deployment_images.append( (imageDeployment.deployment.id, image.id, image.name) )
805 site_login_bases = []
806 for site in Site.objects.all():
807 site_login_bases.append((site.id, site.login_base))
809 context["deployment_nodes"] = deployment_nodes
810 context["deployment_flavors"] = deployment_flavors
811 context["deployment_images"] = deployment_images
812 context["site_login_bases"] = site_login_bases
813 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
815 def formfield_for_foreignkey(self, db_field, request, **kwargs):
816 if db_field.name == 'site':
817 kwargs['queryset'] = Site.select_by_user(request.user)
818 kwargs['widget'] = forms.Select(attrs={'onChange': "update_slice_prefix(this, $($(this).closest('fieldset')[0]).find('.field-name input')[0].id)"})
820 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
822 def queryset(self, request):
823 # admins can see all keys. Users can only see slices they belong to.
824 return Slice.select_by_user(request.user)
826 def get_formsets(self, request, obj=None):
827 for inline in self.get_inline_instances(request, obj):
828 # hide MyInline in the add view
831 if isinstance(inline, SliverInline):
832 inline.model.caller = request.user
833 yield inline.get_formset(request, obj)
835 def get_inline_instances(self, request, obj=None):
836 inlines = super(SliceAdmin, self).get_inline_instances(request, obj)
838 if request.user.is_admin:
839 for inline_class in self.admin_inlines:
840 inlines.append(inline_class(self.model, self.admin_site))
844 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
846 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
848 readonly_fields = ('backend_status_text', )
849 list_display = ('backend_status_icon', 'user', 'slice', 'role')
850 list_display_links = list_display
852 user_readonly_fields = ['user', 'slice', 'role']
853 user_readonly_inlines = []
855 def formfield_for_foreignkey(self, db_field, request, **kwargs):
856 if db_field.name == 'slice':
857 kwargs['queryset'] = Slice.select_by_user(request.user)
859 if db_field.name == 'user':
860 kwargs['queryset'] = User.select_by_user(request.user)
862 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
864 def queryset(self, request):
865 # admins can see all memberships. Users can only see memberships of
866 # slices where they have the admin role.
867 return SlicePrivilege.select_by_user(request.user)
869 def save_model(self, request, obj, form, change):
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)
876 def delete_model(self, request, obj):
877 # update openstack connection to use this site/tenant
878 auth = request.session.get('auth', {})
879 auth['tenant'] = obj.slice.slicename
880 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
884 class ImageAdmin(PlanetStackBaseAdmin):
886 fieldsets = [('Image Details',
887 {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
888 'classes': ['suit-tab suit-tab-general']})
890 readonly_fields = ('backend_status_text', )
892 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
894 inlines = [SliverInline, ImageDeploymentsInline]
896 user_readonly_fields = ['name', 'disk_format', 'container_format']
898 list_display = ['backend_status_icon', 'name']
899 list_display_links = ('backend_status_icon', 'name', )
901 class NodeForm(forms.ModelForm):
904 'site': LinkedSelect,
905 'deployment': LinkedSelect
908 class NodeAdmin(PlanetStackBaseAdmin):
910 list_display = ('backend_status_icon', 'name', 'site', 'deployment')
911 list_display_links = ('backend_status_icon', 'name', )
912 list_filter = ('deployment',)
914 inlines = [TagInline,SliverInline]
915 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
916 readonly_fields = ('backend_status_text', )
918 user_readonly_fields = ['name','site','deployment']
919 user_readonly_inlines = [TagInline,SliverInline]
921 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
924 class SliverForm(forms.ModelForm):
927 ip = forms.CharField(widget=PlainTextWidget)
928 instance_name = forms.CharField(widget=PlainTextWidget)
930 'ip': PlainTextWidget(),
931 'instance_name': PlainTextWidget(),
932 'slice': LinkedSelect,
933 'deploymentNetwork': LinkedSelect,
934 'node': LinkedSelect,
935 'image': LinkedSelect
938 class TagAdmin(PlanetStackBaseAdmin):
939 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
940 list_display_links = list_display
941 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
942 user_readonly_inlines = []
944 class SliverAdmin(PlanetStackBaseAdmin):
947 ('Sliver Details', {'fields': ['backend_status_text', 'slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
949 readonly_fields = ('backend_status_text', )
950 list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'flavor', 'image', 'node', 'deploymentNetwork']
951 list_display_links = ('backend_status_icon', 'ip',)
953 suit_form_tabs =(('general', 'Sliver Details'),
957 inlines = [TagInline]
959 user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image']
961 def formfield_for_foreignkey(self, db_field, request, **kwargs):
962 if db_field.name == 'slice':
963 kwargs['queryset'] = Slice.select_by_user(request.user)
965 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
967 def queryset(self, request):
968 # admins can see all slivers. Users can only see slivers of
969 # the slices they belong to.
970 return Sliver.select_by_user(request.user)
973 def get_formsets(self, request, obj=None):
974 # make some fields read only if we are updating an existing record
976 #self.readonly_fields = ('ip', 'instance_name')
977 self.readonly_fields = ('backend_status_text')
979 self.readonly_fields = ('backend_status_text')
980 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
982 for inline in self.get_inline_instances(request, obj):
983 # hide MyInline in the add view
986 if isinstance(inline, SliverInline):
987 inline.model.caller = request.user
988 yield inline.get_formset(request, obj)
990 #def save_model(self, request, obj, form, change):
991 # # update openstack connection to use this site/tenant
992 # auth = request.session.get('auth', {})
993 # auth['tenant'] = obj.slice.name
994 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
995 # obj.creator = request.user
998 #def delete_model(self, request, obj):
999 # # update openstack connection to use this site/tenant
1000 # auth = request.session.get('auth', {})
1001 # auth['tenant'] = obj.slice.name
1002 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1005 class UserCreationForm(forms.ModelForm):
1006 """A form for creating new users. Includes all the required
1007 fields, plus a repeated password."""
1008 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
1009 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
1013 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
1015 def clean_password2(self):
1016 # Check that the two password entries match
1017 password1 = self.cleaned_data.get("password1")
1018 password2 = self.cleaned_data.get("password2")
1019 if password1 and password2 and password1 != password2:
1020 raise forms.ValidationError("Passwords don't match")
1023 def save(self, commit=True):
1024 # Save the provided password in hashed format
1025 user = super(UserCreationForm, self).save(commit=False)
1026 user.password = self.cleaned_data["password1"]
1027 #user.set_password(self.cleaned_data["password1"])
1033 class UserChangeForm(forms.ModelForm):
1034 """A form for updating users. Includes all the fields on
1035 the user, but replaces the password field with admin's
1036 password hash display field.
1038 password = ReadOnlyPasswordHashField(label='Password',
1039 help_text= '<a href=\"password/\">Change Password</a>.')
1044 def clean_password(self):
1045 # Regardless of what the user provides, return the initial value.
1046 # This is done here, rather than on the field, because the
1047 # field does not have access to the initial value
1048 return self.initial["password"]
1050 class UserDashboardViewInline(PlStackTabularInline):
1051 model = UserDashboardView
1053 suit_classes = 'suit-tab suit-tab-dashboards'
1054 fields = ['user', 'dashboardView', 'order']
1056 class UserAdmin(PermissionCheckingAdminMixin, UserAdmin):
1057 # Note: Make sure PermissionCheckingAdminMixin is listed before
1058 # admin.ModelAdmin in the class declaration.
1063 # The forms to add and change user instances
1064 form = UserChangeForm
1065 add_form = UserCreationForm
1067 # The fields to be used in displaying the User model.
1068 # These override the definitions on the base UserAdmin
1069 # that reference specific fields on auth.User.
1070 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
1071 list_filter = ('site',)
1072 inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
1074 fieldListLoginDetails = ['backend_status_text', 'email','site','password','is_active','is_readonly','is_admin','public_key']
1075 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1078 ('Login Details', {'fields': ['backend_status_text', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1079 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1080 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1081 #('Important dates', {'fields': ('last_login',)}),
1085 'classes': ('wide',),
1086 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')},
1089 readonly_fields = ('backend_status_text', )
1090 search_fields = ('email',)
1091 ordering = ('email',)
1092 filter_horizontal = ()
1094 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1096 def get_form(self, request, obj=None):
1097 # Save obj in thread-local storage, so suit_form_tabs can use it to
1098 # determine whether we're in edit or add mode.
1099 _thread_locals.request = request
1100 _thread_locals.obj = obj
1101 return super(UserAdmin, self).get_form(request, obj)
1104 def suit_form_tabs(self):
1105 if getattr(_thread_locals, "obj", None) is None:
1108 return (('general','Login Details'),
1109 ('contact','Contact Information'),
1110 ('sliceprivileges','Slice Privileges'),
1111 ('siteprivileges','Site Privileges'),
1112 ('deploymentprivileges','Deployment Privileges'),
1113 ('dashboards','Dashboard Views'))
1115 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1116 if db_field.name == 'site':
1117 kwargs['queryset'] = Site.select_by_user(request.user)
1119 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1121 def queryset(self, request):
1122 return User.select_by_user(request.user)
1124 class DashboardViewAdmin(PlanetStackBaseAdmin):
1125 fieldsets = [('Dashboard View Details',
1126 {'fields': ['backend_status_text', 'name', 'url'],
1127 'classes': ['suit-tab suit-tab-general']})
1129 readonly_fields = ('backend_status_text', )
1131 suit_form_tabs =(('general','Dashboard View Details'),)
1133 class ServiceResourceInline(PlStackTabularInline):
1134 model = ServiceResource
1137 class ServiceClassAdmin(PlanetStackBaseAdmin):
1138 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1139 list_display_links = ('backend_status_icon', 'name', )
1140 inlines = [ServiceResourceInline]
1142 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1143 user_readonly_inlines = []
1145 class ReservedResourceInline(PlStackTabularInline):
1146 model = ReservedResource
1148 suit_classes = 'suit-tab suit-tab-reservedresources'
1150 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1151 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1153 if db_field.name == 'resource':
1154 # restrict resources to those that the slice's service class allows
1155 if request._slice is not None:
1156 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1157 if len(field.queryset) > 0:
1158 field.initial = field.queryset.all()[0]
1160 field.queryset = field.queryset.none()
\r
1161 elif db_field.name == 'sliver':
\r
1162 # restrict slivers to those that belong to the slice
\r
1163 if request._slice is not None:
\r
1164 field.queryset = field.queryset.filter(slice = request._slice)
1166 field.queryset = field.queryset.none()
\r
1170 def queryset(self, request):
1171 return ReservedResource.select_by_user(request.user)
1173 class ReservationChangeForm(forms.ModelForm):
1177 'slice' : LinkedSelect
1180 class ReservationAddForm(forms.ModelForm):
1181 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1182 refresh = forms.CharField(widget=forms.HiddenInput())
1185 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1187 def clean_slice(self):
1188 slice = self.cleaned_data.get("slice")
1189 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1191 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1197 'slice' : LinkedSelect
1201 class ReservationAddRefreshForm(ReservationAddForm):
1202 """ This form is displayed when the Reservation Form receives an update
1203 from the Slice dropdown onChange handler. It doesn't validate the
1204 data and doesn't save the data. This will cause the form to be
1208 """ don't validate anything other than slice """
1209 dont_validate_fields = ("startTime", "duration")
1211 def full_clean(self):
1212 result = super(ReservationAddForm, self).full_clean()
1214 for fieldname in self.dont_validate_fields:
1215 if fieldname in self._errors:
1216 del self._errors[fieldname]
1220 """ don't save anything """
1224 class ReservationAdmin(PlanetStackBaseAdmin):
1225 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
1226 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1227 readonly_fields = ('backend_status_text', )
1228 list_display = ('startTime', 'duration')
1229 form = ReservationAddForm
1231 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1233 inlines = [ReservedResourceInline]
1234 user_readonly_fields = fieldList
1236 def add_view(self, request, form_url='', extra_context=None):
1237 timezone.activate(request.user.timezone)
1238 request._refresh = False
1239 request._slice = None
1240 if request.method == 'POST':
1241 # "refresh" will be set to "1" if the form was submitted due to
1242 # a change in the Slice dropdown.
1243 if request.POST.get("refresh","1") == "1":
1244 request._refresh = True
1245 request.POST["refresh"] = "0"
1247 # Keep track of the slice that was selected, so the
1248 # reservedResource inline can filter items for the slice.
1249 request._slice = request.POST.get("slice",None)
1250 if (request._slice is not None):
1251 request._slice = Slice.objects.get(id=request._slice)
1253 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1256 def changelist_view(self, request, extra_context = None):
1257 timezone.activate(request.user.timezone)
1258 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1260 def get_form(self, request, obj=None, **kwargs):
1263 # For changes, set request._slice to the slice already set in the
1265 request._slice = obj.slice
1266 self.form = ReservationChangeForm
1268 if getattr(request, "_refresh", False):
1269 self.form = ReservationAddRefreshForm
1271 self.form = ReservationAddForm
1272 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1274 def get_readonly_fields(self, request, obj=None):
1275 if (obj is not None):
1276 # Prevent slice from being changed after the reservation has been
1282 def queryset(self, request):
1283 return Reservation.select_by_user(request.user)
1285 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1286 list_display = ("backend_status_icon", "name", )
1287 list_display_links = ('backend_status_icon', 'name', )
1288 user_readonly_fields = ['name']
1289 user_readonly_inlines = []
1291 class RouterAdmin(PlanetStackBaseAdmin):
1292 list_display = ("backend_status_icon", "name", )
1293 list_display_links = ('backend_status_icon', 'name', )
1294 user_readonly_fields = ['name']
1295 user_readonly_inlines = []
1297 class RouterInline(PlStackTabularInline):
1298 model = Router.networks.through
1300 verbose_name_plural = "Routers"
1301 verbose_name = "Router"
1302 suit_classes = 'suit-tab suit-tab-routers'
1304 class NetworkParameterInline(PlStackGenericTabularInline):
1305 model = NetworkParameter
1307 verbose_name_plural = "Parameters"
1308 verbose_name = "Parameter"
1309 suit_classes = 'suit-tab suit-tab-netparams'
1310 fields = ['backend_status_icon', 'parameter', 'value']
1311 readonly_fields = ('backend_status_icon', )
1313 class NetworkSliversInline(PlStackTabularInline):
1314 fields = ['backend_status_icon', 'network','sliver','ip']
1315 readonly_fields = ("backend_status_icon", "ip", )
1316 model = NetworkSliver
1317 selflink_fieldname = "sliver"
1319 verbose_name_plural = "Slivers"
1320 verbose_name = "Sliver"
1321 suit_classes = 'suit-tab suit-tab-networkslivers'
1323 class NetworkSlicesInline(PlStackTabularInline):
1324 model = NetworkSlice
1325 selflink_fieldname = "slice"
1327 verbose_name_plural = "Slices"
1328 verbose_name = "Slice"
1329 suit_classes = 'suit-tab suit-tab-networkslices'
1330 fields = ['backend_status_icon', 'network','slice']
1331 readonly_fields = ('backend_status_icon', )
1333 class NetworkAdmin(PlanetStackBaseAdmin):
1334 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1335 list_display_links = ('backend_status_icon', 'name', )
1336 readonly_fields = ("subnet", )
1338 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1341 (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']}),]
1343 readonly_fields = ('backend_status_text', )
1344 user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1347 ('general','Network Details'),
1348 ('netparams', 'Parameters'),
1349 ('networkslivers','Slivers'),
1350 ('networkslices','Slices'),
1351 ('routers','Routers'),
1353 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1354 list_display = ("backend_status_icon", "name", "guaranteedBandwidth", "visibility")
1355 list_display_links = ('backend_status_icon', 'name', )
1356 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1357 user_readonly_inlines = []
1359 class FlavorAdmin(PlanetStackBaseAdmin):
1360 list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1361 list_display_links = ("backend_status_icon", "name")
1362 user_readonly_fields = ("name", "flavor")
1363 fields = ("name", "description", "flavor", "order", "default")
1365 # register a signal that caches the user's credentials when they log in
1366 def cache_credentials(sender, user, request, **kwds):
1367 auth = {'username': request.POST['username'],
1368 'password': request.POST['password']}
1369 request.session['auth'] = auth
1370 user_logged_in.connect(cache_credentials)
1372 def dollar_field(fieldName, short_description):
1373 def newFunc(self, obj):
1375 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1377 x=getattr(obj, fieldName, 0.0)
1379 newFunc.short_description = short_description
1382 def right_dollar_field(fieldName, short_description):
1383 def newFunc(self, obj):
1385 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1386 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1388 x=getattr(obj, fieldName, 0.0)
1390 newFunc.short_description = short_description
1391 newFunc.allow_tags = True
1394 class InvoiceChargeInline(PlStackTabularInline):
1397 verbose_name_plural = "Charges"
1398 verbose_name = "Charge"
1399 exclude = ['account']
1400 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1401 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1405 dollar_amount = right_dollar_field("amount", "Amount")
1407 class InvoiceAdmin(admin.ModelAdmin):
1408 list_display = ("date", "account")
1410 inlines = [InvoiceChargeInline]
1412 fields = ["date", "account", "dollar_amount"]
1413 readonly_fields = ["date", "account", "dollar_amount"]
1415 dollar_amount = dollar_field("amount", "Amount")
1417 class InvoiceInline(PlStackTabularInline):
1420 verbose_name_plural = "Invoices"
1421 verbose_name = "Invoice"
1422 fields = ["date", "dollar_amount"]
1423 readonly_fields = ["date", "dollar_amount"]
1424 suit_classes = 'suit-tab suit-tab-accountinvoice'
1428 dollar_amount = right_dollar_field("amount", "Amount")
1430 class PendingChargeInline(PlStackTabularInline):
1433 verbose_name_plural = "Charges"
1434 verbose_name = "Charge"
1435 exclude = ["invoice"]
1436 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1437 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1438 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1442 def queryset(self, request):
1443 qs = super(PendingChargeInline, self).queryset(request)
1444 qs = qs.filter(state="pending")
1447 dollar_amount = right_dollar_field("amount", "Amount")
1449 class PaymentInline(PlStackTabularInline):
1452 verbose_name_plural = "Payments"
1453 verbose_name = "Payment"
1454 fields = ["date", "dollar_amount"]
1455 readonly_fields = ["date", "dollar_amount"]
1456 suit_classes = 'suit-tab suit-tab-accountpayments'
1460 dollar_amount = right_dollar_field("amount", "Amount")
1462 class AccountAdmin(admin.ModelAdmin):
1463 list_display = ("site", "balance_due")
1465 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1468 (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1470 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1473 ('general','Account Details'),
1474 ('accountinvoice', 'Invoices'),
1475 ('accountpayments', 'Payments'),
1476 ('accountpendingcharges','Pending Charges'),
1479 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1480 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1481 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1483 # Now register the new UserAdmin...
1484 admin.site.register(User, UserAdmin)
1485 # ... and, since we're not using Django's builtin permissions,
1486 # unregister the Group model from admin.
1487 #admin.site.unregister(Group)
1489 #Do not show django evolution in the admin interface
1490 from django_evolution.models import Version, Evolution
1491 #admin.site.unregister(Version)
1492 #admin.site.unregister(Evolution)
1495 # When debugging it is often easier to see all the classes, but for regular use
1496 # only the top-levels should be displayed
1499 admin.site.register(Deployment, DeploymentAdmin)
1500 admin.site.register(Site, SiteAdmin)
1501 admin.site.register(Slice, SliceAdmin)
1502 admin.site.register(Service, ServiceAdmin)
1503 admin.site.register(Reservation, ReservationAdmin)
1504 admin.site.register(Network, NetworkAdmin)
1505 admin.site.register(Router, RouterAdmin)
1506 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1507 admin.site.register(Account, AccountAdmin)
1508 admin.site.register(Invoice, InvoiceAdmin)
1511 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1512 admin.site.register(ServiceClass, ServiceClassAdmin)
1513 #admin.site.register(PlanetStack)
1514 admin.site.register(Tag, TagAdmin)
1515 admin.site.register(DeploymentRole)
1516 admin.site.register(SiteRole)
1517 admin.site.register(SliceRole)
1518 admin.site.register(PlanetStackRole)
1519 admin.site.register(Node, NodeAdmin)
1520 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1521 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1522 admin.site.register(Sliver, SliverAdmin)
1523 admin.site.register(Image, ImageAdmin)
1524 admin.site.register(DashboardView, DashboardViewAdmin)
1525 admin.site.register(Flavor, FlavorAdmin)