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
769 def suit_form_tabs(self):
770 tabs =[('general', 'Slice Details'),
771 ('slicenetworks','Networks'),
772 ('sliceprivileges','Privileges'),
773 ('slivers','Slivers'),
775 ('reservations','Reservations'),
778 request=getattr(_thread_locals, "request", None)
779 if request and request.user.is_admin:
780 tabs.append( ('admin-only', 'Admin-Only') )
784 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
785 deployment_nodes = []
786 for node in Node.objects.all():
787 deployment_nodes.append( (node.deployment.id, node.id, node.name) )
789 deployment_flavors = []
790 for flavor in Flavor.objects.all():
791 for deployment in flavor.deployments.all():
792 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
794 deployment_images = []
795 for image in Image.objects.all():
796 for imageDeployment in image.imagedeployments_set.all():
797 deployment_images.append( (imageDeployment.deployment.id, image.id, image.name) )
799 site_login_bases = []
800 for site in Site.objects.all():
801 site_login_bases.append((site.id, site.login_base))
803 context["deployment_nodes"] = deployment_nodes
804 context["deployment_flavors"] = deployment_flavors
805 context["deployment_images"] = deployment_images
806 context["site_login_bases"] = site_login_bases
807 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
809 def formfield_for_foreignkey(self, db_field, request, **kwargs):
810 if db_field.name == 'site':
811 kwargs['queryset'] = Site.select_by_user(request.user)
812 kwargs['widget'] = forms.Select(attrs={'onChange': "update_slice_prefix(this, $($(this).closest('fieldset')[0]).find('.field-name input')[0].id)"})
814 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
816 def queryset(self, request):
817 # admins can see all keys. Users can only see slices they belong to.
818 return Slice.select_by_user(request.user)
820 def get_formsets(self, request, obj=None):
821 for inline in self.get_inline_instances(request, obj):
822 # hide MyInline in the add view
825 if isinstance(inline, SliverInline):
826 inline.model.caller = request.user
827 yield inline.get_formset(request, obj)
829 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
831 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
833 readonly_fields = ('backend_status_text', )
834 list_display = ('backend_status_icon', 'user', 'slice', 'role')
835 list_display_links = list_display
837 user_readonly_fields = ['user', 'slice', 'role']
838 user_readonly_inlines = []
840 def formfield_for_foreignkey(self, db_field, request, **kwargs):
841 if db_field.name == 'slice':
842 kwargs['queryset'] = Slice.select_by_user(request.user)
844 if db_field.name == 'user':
845 kwargs['queryset'] = User.select_by_user(request.user)
847 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
849 def queryset(self, request):
850 # admins can see all memberships. Users can only see memberships of
851 # slices where they have the admin role.
852 return SlicePrivilege.select_by_user(request.user)
854 def save_model(self, request, obj, form, change):
855 # update openstack connection to use this site/tenant
856 auth = request.session.get('auth', {})
857 auth['tenant'] = obj.slice.slicename
858 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
861 def delete_model(self, request, obj):
862 # update openstack connection to use this site/tenant
863 auth = request.session.get('auth', {})
864 auth['tenant'] = obj.slice.slicename
865 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
869 class ImageAdmin(PlanetStackBaseAdmin):
871 fieldsets = [('Image Details',
872 {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
873 'classes': ['suit-tab suit-tab-general']})
875 readonly_fields = ('backend_status_text', )
877 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
879 inlines = [SliverInline, ImageDeploymentsInline]
881 user_readonly_fields = ['name', 'disk_format', 'container_format']
883 list_display = ['backend_status_icon', 'name']
884 list_display_links = ('backend_status_icon', 'name', )
886 class NodeForm(forms.ModelForm):
889 'site': LinkedSelect,
890 'deployment': LinkedSelect
893 class NodeAdmin(PlanetStackBaseAdmin):
895 list_display = ('backend_status_icon', 'name', 'site', 'deployment')
896 list_display_links = ('backend_status_icon', 'name', )
897 list_filter = ('deployment',)
899 inlines = [TagInline,SliverInline]
900 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
901 readonly_fields = ('backend_status_text', )
903 user_readonly_fields = ['name','site','deployment']
904 user_readonly_inlines = [TagInline,SliverInline]
906 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
909 class SliverForm(forms.ModelForm):
912 ip = forms.CharField(widget=PlainTextWidget)
913 instance_name = forms.CharField(widget=PlainTextWidget)
915 'ip': PlainTextWidget(),
916 'instance_name': PlainTextWidget(),
917 'slice': LinkedSelect,
918 'deploymentNetwork': LinkedSelect,
919 'node': LinkedSelect,
920 'image': LinkedSelect
923 class TagAdmin(PlanetStackBaseAdmin):
924 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
925 list_display_links = list_display
926 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
927 user_readonly_inlines = []
929 class SliverAdmin(PlanetStackBaseAdmin):
932 ('Sliver Details', {'fields': ['backend_status_text', 'slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
934 readonly_fields = ('backend_status_text', )
935 list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'flavor', 'image', 'node', 'deploymentNetwork']
936 list_display_links = ('backend_status_icon', 'ip',)
938 suit_form_tabs =(('general', 'Sliver Details'),
942 inlines = [TagInline]
944 user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image']
946 def formfield_for_foreignkey(self, db_field, request, **kwargs):
947 if db_field.name == 'slice':
948 kwargs['queryset'] = Slice.select_by_user(request.user)
950 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
952 def queryset(self, request):
953 # admins can see all slivers. Users can only see slivers of
954 # the slices they belong to.
955 return Sliver.select_by_user(request.user)
958 def get_formsets(self, request, obj=None):
959 # make some fields read only if we are updating an existing record
961 #self.readonly_fields = ('ip', 'instance_name')
962 self.readonly_fields = ('backend_status_text',)
964 self.readonly_fields = ('backend_status_text',)
965 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
967 for inline in self.get_inline_instances(request, obj):
968 # hide MyInline in the add view
971 if isinstance(inline, SliverInline):
972 inline.model.caller = request.user
973 yield inline.get_formset(request, obj)
975 #def save_model(self, request, obj, form, change):
976 # # update openstack connection to use this site/tenant
977 # auth = request.session.get('auth', {})
978 # auth['tenant'] = obj.slice.name
979 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
980 # obj.creator = request.user
983 #def delete_model(self, request, obj):
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)
990 class UserCreationForm(forms.ModelForm):
991 """A form for creating new users. Includes all the required
992 fields, plus a repeated password."""
993 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
994 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
998 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
1000 def clean_password2(self):
1001 # Check that the two password entries match
1002 password1 = self.cleaned_data.get("password1")
1003 password2 = self.cleaned_data.get("password2")
1004 if password1 and password2 and password1 != password2:
1005 raise forms.ValidationError("Passwords don't match")
1008 def save(self, commit=True):
1009 # Save the provided password in hashed format
1010 user = super(UserCreationForm, self).save(commit=False)
1011 user.password = self.cleaned_data["password1"]
1012 #user.set_password(self.cleaned_data["password1"])
1018 class UserChangeForm(forms.ModelForm):
1019 """A form for updating users. Includes all the fields on
1020 the user, but replaces the password field with admin's
1021 password hash display field.
1023 password = ReadOnlyPasswordHashField(label='Password',
1024 help_text= '<a href=\"password/\">Change Password</a>.')
1029 def clean_password(self):
1030 # Regardless of what the user provides, return the initial value.
1031 # This is done here, rather than on the field, because the
1032 # field does not have access to the initial value
1033 return self.initial["password"]
1035 class UserDashboardViewInline(PlStackTabularInline):
1036 model = UserDashboardView
1038 suit_classes = 'suit-tab suit-tab-dashboards'
1039 fields = ['user', 'dashboardView', 'order']
1041 class UserAdmin(PermissionCheckingAdminMixin, UserAdmin):
1042 # Note: Make sure PermissionCheckingAdminMixin is listed before
1043 # admin.ModelAdmin in the class declaration.
1048 # The forms to add and change user instances
1049 form = UserChangeForm
1050 add_form = UserCreationForm
1052 # The fields to be used in displaying the User model.
1053 # These override the definitions on the base UserAdmin
1054 # that reference specific fields on auth.User.
1055 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
1056 list_filter = ('site',)
1057 inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
1059 fieldListLoginDetails = ['backend_status_text', 'email','site','password','is_active','is_readonly','is_admin','public_key']
1060 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1063 ('Login Details', {'fields': ['backend_status_text', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1064 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1065 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1066 #('Important dates', {'fields': ('last_login',)}),
1070 'classes': ('wide',),
1071 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')},
1074 readonly_fields = ('backend_status_text', )
1075 search_fields = ('email',)
1076 ordering = ('email',)
1077 filter_horizontal = ()
1079 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1082 def suit_form_tabs(self):
1083 if getattr(_thread_locals, "obj", None) is None:
1086 return (('general','Login Details'),
1087 ('contact','Contact Information'),
1088 ('sliceprivileges','Slice Privileges'),
1089 ('siteprivileges','Site Privileges'),
1090 ('deploymentprivileges','Deployment Privileges'),
1091 ('dashboards','Dashboard Views'))
1093 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1094 if db_field.name == 'site':
1095 kwargs['queryset'] = Site.select_by_user(request.user)
1097 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1099 def queryset(self, request):
1100 return User.select_by_user(request.user)
1102 class DashboardViewAdmin(PlanetStackBaseAdmin):
1103 fieldsets = [('Dashboard View Details',
1104 {'fields': ['backend_status_text', 'name', 'url'],
1105 'classes': ['suit-tab suit-tab-general']})
1107 readonly_fields = ('backend_status_text', )
1109 suit_form_tabs =(('general','Dashboard View Details'),)
1111 class ServiceResourceInline(PlStackTabularInline):
1112 model = ServiceResource
1115 class ServiceClassAdmin(PlanetStackBaseAdmin):
1116 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1117 list_display_links = ('backend_status_icon', 'name', )
1118 inlines = [ServiceResourceInline]
1120 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1121 user_readonly_inlines = []
1123 class ReservedResourceInline(PlStackTabularInline):
1124 model = ReservedResource
1126 suit_classes = 'suit-tab suit-tab-reservedresources'
1128 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1129 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1131 if db_field.name == 'resource':
1132 # restrict resources to those that the slice's service class allows
1133 if request._slice is not None:
1134 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1135 if len(field.queryset) > 0:
1136 field.initial = field.queryset.all()[0]
1138 field.queryset = field.queryset.none()
\r
1139 elif db_field.name == 'sliver':
\r
1140 # restrict slivers to those that belong to the slice
\r
1141 if request._slice is not None:
\r
1142 field.queryset = field.queryset.filter(slice = request._slice)
1144 field.queryset = field.queryset.none()
\r
1148 def queryset(self, request):
1149 return ReservedResource.select_by_user(request.user)
1151 class ReservationChangeForm(forms.ModelForm):
1155 'slice' : LinkedSelect
1158 class ReservationAddForm(forms.ModelForm):
1159 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1160 refresh = forms.CharField(widget=forms.HiddenInput())
1163 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1165 def clean_slice(self):
1166 slice = self.cleaned_data.get("slice")
1167 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1169 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1175 'slice' : LinkedSelect
1179 class ReservationAddRefreshForm(ReservationAddForm):
1180 """ This form is displayed when the Reservation Form receives an update
1181 from the Slice dropdown onChange handler. It doesn't validate the
1182 data and doesn't save the data. This will cause the form to be
1186 """ don't validate anything other than slice """
1187 dont_validate_fields = ("startTime", "duration")
1189 def full_clean(self):
1190 result = super(ReservationAddForm, self).full_clean()
1192 for fieldname in self.dont_validate_fields:
1193 if fieldname in self._errors:
1194 del self._errors[fieldname]
1198 """ don't save anything """
1202 class ReservationAdmin(PlanetStackBaseAdmin):
1203 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
1204 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1205 readonly_fields = ('backend_status_text', )
1206 list_display = ('startTime', 'duration')
1207 form = ReservationAddForm
1209 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1211 inlines = [ReservedResourceInline]
1212 user_readonly_fields = fieldList
1214 def add_view(self, request, form_url='', extra_context=None):
1215 timezone.activate(request.user.timezone)
1216 request._refresh = False
1217 request._slice = None
1218 if request.method == 'POST':
1219 # "refresh" will be set to "1" if the form was submitted due to
1220 # a change in the Slice dropdown.
1221 if request.POST.get("refresh","1") == "1":
1222 request._refresh = True
1223 request.POST["refresh"] = "0"
1225 # Keep track of the slice that was selected, so the
1226 # reservedResource inline can filter items for the slice.
1227 request._slice = request.POST.get("slice",None)
1228 if (request._slice is not None):
1229 request._slice = Slice.objects.get(id=request._slice)
1231 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1234 def changelist_view(self, request, extra_context = None):
1235 timezone.activate(request.user.timezone)
1236 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1238 def get_form(self, request, obj=None, **kwargs):
1241 # For changes, set request._slice to the slice already set in the
1243 request._slice = obj.slice
1244 self.form = ReservationChangeForm
1246 if getattr(request, "_refresh", False):
1247 self.form = ReservationAddRefreshForm
1249 self.form = ReservationAddForm
1250 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1252 def get_readonly_fields(self, request, obj=None):
1253 if (obj is not None):
1254 # Prevent slice from being changed after the reservation has been
1260 def queryset(self, request):
1261 return Reservation.select_by_user(request.user)
1263 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1264 list_display = ("backend_status_icon", "name", )
1265 list_display_links = ('backend_status_icon', 'name', )
1266 user_readonly_fields = ['name']
1267 user_readonly_inlines = []
1269 class RouterAdmin(PlanetStackBaseAdmin):
1270 list_display = ("backend_status_icon", "name", )
1271 list_display_links = ('backend_status_icon', 'name', )
1272 user_readonly_fields = ['name']
1273 user_readonly_inlines = []
1275 class RouterInline(PlStackTabularInline):
1276 model = Router.networks.through
1278 verbose_name_plural = "Routers"
1279 verbose_name = "Router"
1280 suit_classes = 'suit-tab suit-tab-routers'
1282 class NetworkParameterInline(PlStackGenericTabularInline):
1283 model = NetworkParameter
1285 verbose_name_plural = "Parameters"
1286 verbose_name = "Parameter"
1287 suit_classes = 'suit-tab suit-tab-netparams'
1288 fields = ['backend_status_icon', 'parameter', 'value']
1289 readonly_fields = ('backend_status_icon', )
1291 class NetworkSliversInline(PlStackTabularInline):
1292 fields = ['backend_status_icon', 'network','sliver','ip']
1293 readonly_fields = ("backend_status_icon", "ip", )
1294 model = NetworkSliver
1295 selflink_fieldname = "sliver"
1297 verbose_name_plural = "Slivers"
1298 verbose_name = "Sliver"
1299 suit_classes = 'suit-tab suit-tab-networkslivers'
1301 class NetworkSlicesInline(PlStackTabularInline):
1302 model = NetworkSlice
1303 selflink_fieldname = "slice"
1305 verbose_name_plural = "Slices"
1306 verbose_name = "Slice"
1307 suit_classes = 'suit-tab suit-tab-networkslices'
1308 fields = ['backend_status_icon', 'network','slice']
1309 readonly_fields = ('backend_status_icon', )
1311 class NetworkDeploymentsInline(PlStackTabularInline):
1312 model = NetworkDeployments
1314 verbose_name_plural = "Network Deployments"
1315 verbose_name = "Network Deployment"
1316 suit_classes = 'suit-tab suit-tab-admin-only'
1317 fields = ['backend_status_icon', 'deployment','net_id','subnet_id']
1318 readonly_fields = ('backend_status_icon', )
1320 class NetworkAdmin(PlanetStackBaseAdmin):
1321 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1322 list_display_links = ('backend_status_icon', 'name', )
1323 readonly_fields = ("subnet", )
1325 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1326 admin_inlines = [NetworkDeploymentsInline]
1329 (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']}),]
1331 readonly_fields = ('backend_status_text', )
1332 user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1335 def suit_form_tabs(self):
1336 tabs=[('general','Network Details'),
1337 ('netparams', 'Parameters'),
1338 ('networkslivers','Slivers'),
1339 ('networkslices','Slices'),
1340 ('routers','Routers'),
1343 request=getattr(_thread_locals, "request", None)
1344 if request and request.user.is_admin:
1345 tabs.append( ('admin-only', 'Admin-Only') )
1350 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1351 list_display = ("backend_status_icon", "name", "guaranteedBandwidth", "visibility")
1352 list_display_links = ('backend_status_icon', 'name', )
1353 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1354 user_readonly_inlines = []
1356 class FlavorAdmin(PlanetStackBaseAdmin):
1357 list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1358 list_display_links = ("backend_status_icon", "name")
1359 user_readonly_fields = ("name", "flavor")
1360 fields = ("name", "description", "flavor", "order", "default")
1362 # register a signal that caches the user's credentials when they log in
1363 def cache_credentials(sender, user, request, **kwds):
1364 auth = {'username': request.POST['username'],
1365 'password': request.POST['password']}
1366 request.session['auth'] = auth
1367 user_logged_in.connect(cache_credentials)
1369 def dollar_field(fieldName, short_description):
1370 def newFunc(self, obj):
1372 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1374 x=getattr(obj, fieldName, 0.0)
1376 newFunc.short_description = short_description
1379 def right_dollar_field(fieldName, short_description):
1380 def newFunc(self, obj):
1382 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1383 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1385 x=getattr(obj, fieldName, 0.0)
1387 newFunc.short_description = short_description
1388 newFunc.allow_tags = True
1391 class InvoiceChargeInline(PlStackTabularInline):
1394 verbose_name_plural = "Charges"
1395 verbose_name = "Charge"
1396 exclude = ['account']
1397 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1398 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1402 dollar_amount = right_dollar_field("amount", "Amount")
1404 class InvoiceAdmin(admin.ModelAdmin):
1405 list_display = ("date", "account")
1407 inlines = [InvoiceChargeInline]
1409 fields = ["date", "account", "dollar_amount"]
1410 readonly_fields = ["date", "account", "dollar_amount"]
1412 dollar_amount = dollar_field("amount", "Amount")
1414 class InvoiceInline(PlStackTabularInline):
1417 verbose_name_plural = "Invoices"
1418 verbose_name = "Invoice"
1419 fields = ["date", "dollar_amount"]
1420 readonly_fields = ["date", "dollar_amount"]
1421 suit_classes = 'suit-tab suit-tab-accountinvoice'
1425 dollar_amount = right_dollar_field("amount", "Amount")
1427 class PendingChargeInline(PlStackTabularInline):
1430 verbose_name_plural = "Charges"
1431 verbose_name = "Charge"
1432 exclude = ["invoice"]
1433 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1434 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1435 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1439 def queryset(self, request):
1440 qs = super(PendingChargeInline, self).queryset(request)
1441 qs = qs.filter(state="pending")
1444 dollar_amount = right_dollar_field("amount", "Amount")
1446 class PaymentInline(PlStackTabularInline):
1449 verbose_name_plural = "Payments"
1450 verbose_name = "Payment"
1451 fields = ["date", "dollar_amount"]
1452 readonly_fields = ["date", "dollar_amount"]
1453 suit_classes = 'suit-tab suit-tab-accountpayments'
1457 dollar_amount = right_dollar_field("amount", "Amount")
1459 class AccountAdmin(admin.ModelAdmin):
1460 list_display = ("site", "balance_due")
1462 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1465 (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1467 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1470 ('general','Account Details'),
1471 ('accountinvoice', 'Invoices'),
1472 ('accountpayments', 'Payments'),
1473 ('accountpendingcharges','Pending Charges'),
1476 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1477 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1478 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1480 # Now register the new UserAdmin...
1481 admin.site.register(User, UserAdmin)
1482 # ... and, since we're not using Django's builtin permissions,
1483 # unregister the Group model from admin.
1484 #admin.site.unregister(Group)
1486 #Do not show django evolution in the admin interface
1487 from django_evolution.models import Version, Evolution
1488 #admin.site.unregister(Version)
1489 #admin.site.unregister(Evolution)
1492 # When debugging it is often easier to see all the classes, but for regular use
1493 # only the top-levels should be displayed
1496 admin.site.register(Deployment, DeploymentAdmin)
1497 admin.site.register(Site, SiteAdmin)
1498 admin.site.register(Slice, SliceAdmin)
1499 admin.site.register(Service, ServiceAdmin)
1500 admin.site.register(Reservation, ReservationAdmin)
1501 admin.site.register(Network, NetworkAdmin)
1502 admin.site.register(Router, RouterAdmin)
1503 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1504 admin.site.register(Account, AccountAdmin)
1505 admin.site.register(Invoice, InvoiceAdmin)
1508 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1509 admin.site.register(ServiceClass, ServiceClassAdmin)
1510 #admin.site.register(PlanetStack)
1511 admin.site.register(Tag, TagAdmin)
1512 admin.site.register(DeploymentRole)
1513 admin.site.register(SiteRole)
1514 admin.site.register(SliceRole)
1515 admin.site.register(PlanetStackRole)
1516 admin.site.register(Node, NodeAdmin)
1517 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1518 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1519 admin.site.register(Sliver, SliverAdmin)
1520 admin.site.register(Image, ImageAdmin)
1521 admin.site.register(DashboardView, DashboardViewAdmin)
1522 admin.site.register(Flavor, FlavorAdmin)