1 from core.models import Site
2 from core.models import *
3 from openstack.manager import OpenStackManager
5 from django.contrib import admin
6 from django.contrib.auth.models import Group
7 from django import forms
8 from django.utils.safestring import mark_safe
9 from django.contrib.auth.admin import UserAdmin
10 from django.contrib.admin.widgets import FilteredSelectMultiple
11 from django.contrib.auth.forms import ReadOnlyPasswordHashField
12 from django.contrib.auth.signals import user_logged_in
13 from django.utils import timezone
14 from django.contrib.contenttypes import generic
15 from suit.widgets import LinkedSelect
16 from django.core.exceptions import PermissionDenied
17 from django.core.urlresolvers import reverse, NoReverseMatch
19 import django_evolution
21 def backend_icon(obj): # backend_status, enacted, updated):
22 #return "%s %s %s" % (str(obj.updated), str(obj.enacted), str(obj.backend_status))
23 if (obj.enacted is not None) and obj.enacted >= obj.updated:
24 return '<span style="min-width:16px;"><img src="/static/admin/img/icon_success.gif"></span>'
26 if obj.backend_status == "Provisioning in progress" or obj.backend_status=="":
27 return '<span style="min-width:16px;" title="%s"><img src="/static/admin/img/icon_clock.gif"></span>' % obj.backend_status
29 return '<span style="min-width:16px;" title="%s"><img src="/static/admin/img/icon_error.gif"></span>' % obj.backend_status
31 def backend_text(obj):
32 icon = backend_icon(obj)
33 if (obj.enacted is not None) and obj.enacted >= obj.updated:
34 return "%s %s" % (icon, "successfully enacted") # enacted on %s" % str(obj.enacted))
36 return "%s %s" % (icon, obj.backend_status)
38 class PlainTextWidget(forms.HiddenInput):
41 def render(self, name, value, attrs=None):
44 return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
46 class ReadOnlyAwareAdmin(admin.ModelAdmin):
48 def has_add_permission(self, request, obj=None):
49 return (not self.__user_is_readonly(request))
51 def has_delete_permission(self, request, obj=None):
52 return (not self.__user_is_readonly(request))
54 def save_model(self, request, obj, form, change):
55 if self.__user_is_readonly(request):
56 raise PermissionDenied
59 return super(ReadOnlyAwareAdmin, self).save_model(request, obj, form, change)
61 def get_actions(self,request):
62 actions = super(ReadOnlyAwareAdmin,self).get_actions(request)
64 if self.__user_is_readonly(request):
65 if 'delete_selected' in actions:
66 del actions['delete_selected']
70 def change_view(self,request,object_id, extra_context=None):
71 if self.__user_is_readonly(request):
72 if not hasattr(self, "readonly_save"):
\r
73 # save the original readonly fields
\r
74 self.readonly_save = self.readonly_fields
\r
75 self.inlines_save = self.inlines
\r
76 if hasattr(self, "user_readonly_fields"):
\r
77 self.readonly_fields=self.user_readonly_fields
\r
78 if hasattr(self, "user_readonly_inlines"):
\r
79 self.inlines = self.user_readonly_inlines
\r
81 if hasattr(self, "readonly_save"):
\r
82 # restore the original readonly fields
\r
83 self.readonly_fields = self.readonly_save
\r
84 if hasattr(self, "inlines_save"):
\r
85 self.inlines = self.inlines_save
88 return super(ReadOnlyAwareAdmin, self).change_view(request, object_id, extra_context=extra_context)
89 except PermissionDenied:
91 if request.method == 'POST':
92 raise PermissionDenied
93 request.readonly = True
94 return super(ReadOnlyAwareAdmin, self).change_view(request, object_id, extra_context=extra_context)
96 def __user_is_readonly(self, request):
97 return request.user.isReadOnlyUser()
99 def backend_status_text(self, obj):
100 return mark_safe(backend_text(obj))
102 def backend_status_icon(self, obj):
103 return mark_safe(backend_icon(obj))
104 backend_status_icon.short_description = ""
107 class SingletonAdmin (ReadOnlyAwareAdmin):
108 def has_add_permission(self, request):
109 if not super(SingletonAdmin, self).has_add_permission(request):
112 num_objects = self.model.objects.count()
119 class PlStackTabularInline(admin.TabularInline):
120 def __init__(self, *args, **kwargs):
121 super(PlStackTabularInline, self).__init__(*args, **kwargs)
123 # InlineModelAdmin as no get_fields() method, so in order to add
124 # the selflink field, we override __init__ to modify self.fields and
125 # self.readonly_fields.
127 self.setup_selflink()
129 def get_change_url(self, model, id):
130 """ Get the URL to a change form in the admin for this model """
131 reverse_path = "admin:%s_change" % (model._meta.db_table)
133 url = reverse(reverse_path, args=(id,))
134 except NoReverseMatch:
139 def setup_selflink(self):
140 if hasattr(self, "selflink_fieldname"):
141 """ self.selflink_model can be defined to punch through a relation
142 to its target object. For example, in SliceNetworkInline, set
143 selflink_model = "network", and the URL will lead to the Network
144 object instead of trying to bring up a change view of the
147 self.selflink_model = getattr(self.model,self.selflink_fieldname).field.rel.to
149 self.selflink_model = self.model
151 url = self.get_change_url(self.selflink_model, 0)
153 # We don't have an admin for this object, so don't create the
158 # Since we need to add "selflink" to the field list, we need to create
159 # self.fields if it is None.
160 if (self.fields is None):
162 for f in self.model._meta.fields:
163 if f.editable and f.name != "id":
164 self.fields.append(f.name)
166 self.fields = tuple(self.fields) + ("selflink", )
168 if self.readonly_fields is None:
169 self.readonly_fields = ()
171 self.readonly_fields = tuple(self.readonly_fields) + ("selflink", )
173 def selflink(self, obj):
174 if hasattr(self, "selflink_fieldname"):
175 obj = getattr(obj, self.selflink_fieldname)
178 url = self.get_change_url(self.selflink_model, obj.id)
179 return "<a href='%s'>Details</a>" % str(url)
181 return "Not present"
\r
183 selflink.allow_tags = True
184 selflink.short_description = "Details"
186 def has_add_permission(self, request):
187 return not request.user.isReadOnlyUser()
189 def get_readonly_fields(self, request, obj=None):
190 readonly_fields = list(self.readonly_fields)[:]
191 if request.user.isReadOnlyUser():
192 for field in self.fields:
193 if not field in readonly_fields:
194 readonly_fields.append(field)
195 return readonly_fields
197 def backend_status_icon(self, obj):
198 return mark_safe(backend_icon(obj))
199 backend_status_icon.short_description = ""
201 class PlStackGenericTabularInline(generic.GenericTabularInline):
202 def has_add_permission(self, request):
203 return not request.user.isReadOnlyUser()
205 def get_readonly_fields(self, request, obj=None):
206 readonly_fields = list(self.readonly_fields)[:]
207 if request.user.isReadOnlyUser():
208 for field in self.fields:
209 if not field in readonly_fields:
210 readonly_fields.append(field)
211 return readonly_fields
213 def backend_status_icon(self, obj):
214 return mark_safe(backend_icon(obj))
215 backend_status_icon.short_description = ""
217 class ReservationInline(PlStackTabularInline):
220 suit_classes = 'suit-tab suit-tab-reservations'
222 def queryset(self, request):
223 return Reservation.select_by_user(request.user)
225 class TagInline(PlStackGenericTabularInline):
228 suit_classes = 'suit-tab suit-tab-tags'
229 fields = ['service', 'name', 'value']
231 def queryset(self, request):
232 return Tag.select_by_user(request.user)
234 class NetworkLookerUpper:
235 """ This is a callable that looks up a network name in a sliver and returns
236 the ip address for that network.
239 byNetworkName = {} # class variable
241 def __init__(self, name):
242 self.short_description = name
244 self.network_name = name
246 def __call__(self, obj):
248 for nbs in obj.networksliver_set.all():
249 if (nbs.network.name == self.network_name):
254 return self.network_name
257 def get(network_name):
258 """ We want to make sure we alwars return the same NetworkLookerUpper
259 because sometimes django will cause them to be instantiated multiple
260 times (and we don't want different ones in form.fields vs
261 SliverInline.readonly_fields).
263 if network_name not in NetworkLookerUpper.byNetworkName:
264 NetworkLookerUpper.byNetworkName[network_name] = NetworkLookerUpper(network_name)
265 return NetworkLookerUpper.byNetworkName[network_name]
267 class SliverInline(PlStackTabularInline):
269 fields = ['backend_status_icon', 'all_ips_string', 'instance_name', 'slice', 'deploymentNetwork', 'flavor', 'image', 'node']
271 readonly_fields = ['backend_status_icon', 'all_ips_string', 'instance_name']
272 suit_classes = 'suit-tab suit-tab-slivers'
274 def queryset(self, request):
275 return Sliver.select_by_user(request.user)
277 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
278 if db_field.name == 'deploymentNetwork':
279 kwargs['queryset'] = Deployment.select_by_acl(request.user)
280 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_deployment_changed(this);"})
281 elif db_field.name == 'flavor':
282 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_flavor_changed(this);"})
284 field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
288 class SiteInline(PlStackTabularInline):
291 suit_classes = 'suit-tab suit-tab-sites'
293 def queryset(self, request):
294 return Site.select_by_user(request.user)
296 class UserInline(PlStackTabularInline):
298 fields = ['backend_status_icon', 'email', 'firstname', 'lastname']
299 readonly_fields = ('backend_status_icon', )
301 suit_classes = 'suit-tab suit-tab-users'
303 def queryset(self, request):
304 return User.select_by_user(request.user)
306 class SliceInline(PlStackTabularInline):
308 fields = ['backend_status_icon', 'name', 'site', 'serviceClass', 'service']
309 readonly_fields = ('backend_status_icon', )
311 suit_classes = 'suit-tab suit-tab-slices'
313 def queryset(self, request):
314 return Slice.select_by_user(request.user)
316 class NodeInline(PlStackTabularInline):
319 suit_classes = 'suit-tab suit-tab-nodes'
320 fields = ['backend_status_icon', 'name','deployment','site']
321 readonly_fields = ('backend_status_icon', )
323 class DeploymentPrivilegeInline(PlStackTabularInline):
324 model = DeploymentPrivilege
326 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
327 fields = ['backend_status_icon', 'user','role','deployment']
328 readonly_fields = ('backend_status_icon', )
330 def queryset(self, request):
331 return DeploymentPrivilege.select_by_user(request.user)
333 class SitePrivilegeInline(PlStackTabularInline):
334 model = SitePrivilege
336 suit_classes = 'suit-tab suit-tab-siteprivileges'
337 fields = ['backend_status_icon', 'user','site', 'role']
338 readonly_fields = ('backend_status_icon', )
340 def formfield_for_foreignkey(self, db_field, request, **kwargs):
341 if db_field.name == 'site':
342 kwargs['queryset'] = Site.select_by_user(request.user)
344 if db_field.name == 'user':
345 kwargs['queryset'] = User.select_by_user(request.user)
346 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
348 def queryset(self, request):
349 return SitePrivilege.select_by_user(request.user)
351 class SiteDeploymentInline(PlStackTabularInline):
352 model = SiteDeployments
354 suit_classes = 'suit-tab suit-tab-deployments'
355 fields = ['backend_status_icon', 'deployment','site']
356 readonly_fields = ('backend_status_icon', )
358 def formfield_for_foreignkey(self, db_field, request, **kwargs):
359 if db_field.name == 'site':
360 kwargs['queryset'] = Site.select_by_user(request.user)
362 if db_field.name == 'deployment':
363 kwargs['queryset'] = Deployment.select_by_user(request.user)
364 return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
366 def queryset(self, request):
367 return SiteDeployments.select_by_user(request.user)
370 class SlicePrivilegeInline(PlStackTabularInline):
371 model = SlicePrivilege
372 suit_classes = 'suit-tab suit-tab-sliceprivileges'
374 fields = ('backend_status_icon', 'user', 'slice', 'role')
375 readonly_fields = ('backend_status_icon', )
377 def formfield_for_foreignkey(self, db_field, request, **kwargs):
378 if db_field.name == 'slice':
379 kwargs['queryset'] = Slice.select_by_user(request.user)
380 if db_field.name == 'user':
381 kwargs['queryset'] = User.select_by_user(request.user)
383 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
385 def queryset(self, request):
386 return SlicePrivilege.select_by_user(request.user)
388 class SliceNetworkInline(PlStackTabularInline):
389 model = Network.slices.through
390 selflink_fieldname = "network"
392 verbose_name = "Network Connection"
393 verbose_name_plural = "Network Connections"
394 suit_classes = 'suit-tab suit-tab-slicenetworks'
395 fields = ['backend_status_icon', 'network']
396 readonly_fields = ('backend_status_icon', )
398 class ImageDeploymentsInline(PlStackTabularInline):
399 model = ImageDeployments
401 verbose_name = "Image Deployments"
402 verbose_name_plural = "Image Deployments"
403 suit_classes = 'suit-tab suit-tab-imagedeployments'
404 fields = ['backend_status_icon', 'image', 'deployment', 'glance_image_id']
405 readonly_fields = ['backend_status_icon', 'glance_image_id']
407 class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
410 def save_model(self, request, obj, form, change):
411 obj.caller = request.user
412 # update openstack connection to use this site/tenant
413 obj.save_by_user(request.user)
415 def delete_model(self, request, obj):
416 obj.delete_by_user(request.user)
418 def save_formset(self, request, form, formset, change):
419 instances = formset.save(commit=False)
420 for instance in instances:
421 instance.save_by_user(request.user)
424 class SliceRoleAdmin(PlanetStackBaseAdmin):
428 class SiteRoleAdmin(PlanetStackBaseAdmin):
432 class DeploymentAdminForm(forms.ModelForm):
433 sites = forms.ModelMultipleChoiceField(
434 queryset=Site.objects.all(),
436 help_text="Select which sites are allowed to host nodes in this deployment",
437 widget=FilteredSelectMultiple(
438 verbose_name=('Sites'), is_stacked=False
441 images = forms.ModelMultipleChoiceField(
442 queryset=Image.objects.all(),
444 help_text="Select which images should be deployed on this deployment",
445 widget=FilteredSelectMultiple(
446 verbose_name=('Images'), is_stacked=False
449 flavors = forms.ModelMultipleChoiceField(
450 queryset=Flavor.objects.all(),
452 help_text="Select which flavors should be usable on this deployment",
453 widget=FilteredSelectMultiple(
454 verbose_name=('Flavors'), is_stacked=False
459 many_to_many = ["flavors",]
461 def __init__(self, *args, **kwargs):
462 request = kwargs.pop('request', None)
463 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
465 self.fields['accessControl'].initial = "allow site " + request.user.site.name
467 if self.instance and self.instance.pk:
468 self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments_set.all()]
469 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments_set.all()]
470 self.fields['flavors'].initial = self.instance.flavors.all()
472 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
473 """ helper function for handling m2m relations from the MultipleChoiceField
475 this_obj: the source object we want to link from
477 selected_objs: a list of destination objects we want to link to
479 all_relations: the full set of relations involving this_obj, including ones we don't want
481 relation_class: the class that implements the relation from source to dest
483 local_attrname: field name representing this_obj in relation_class
485 foreign_attrname: field name representing selected_objs in relation_class
487 This function will remove all newobjclass relations from this_obj
488 that are not contained in selected_objs, and add any relations that
489 are in selected_objs but don't exist in the data model yet.
492 existing_dest_objs = []
493 for relation in list(all_relations):
494 if getattr(relation, foreign_attrname) not in selected_objs:
495 #print "deleting site", sdp.site
498 existing_dest_objs.append(getattr(relation, foreign_attrname))
500 for dest_obj in selected_objs:
501 if dest_obj not in existing_dest_objs:
502 #print "adding site", site
503 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
504 relation = relation_class(**kwargs)
507 def save(self, commit=True):
508 deployment = super(DeploymentAdminForm, self).save(commit=False)
510 deployment.flavors = self.cleaned_data['flavors']
516 # save_m2m() doesn't seem to work with 'through' relations. So we
517 # create/destroy the through models ourselves. There has to be
520 self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments_set.all(), SiteDeployments, "deployment", "site")
521 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments_set.all(), ImageDeployments, "deployment", "image")
527 class DeploymentAdminROForm(DeploymentAdminForm):
528 def save(self, commit=True):
529 raise PermissionDenied
531 class SiteAssocInline(PlStackTabularInline):
532 model = Site.deployments.through
534 suit_classes = 'suit-tab suit-tab-sites'
536 class DeploymentAdmin(PlanetStackBaseAdmin):
538 fieldList = ['backend_status_text', 'name', 'sites', 'images', 'flavors', 'accessControl']
539 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
540 inlines = [DeploymentPrivilegeInline,NodeInline,TagInline] # ,ImageDeploymentsInline]
541 list_display = ['backend_status_icon', 'name']
542 list_display_links = ('backend_status_icon', 'name', )
543 readonly_fields = ('backend_status_text', )
545 user_readonly_fields = ['name']
547 suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags')) # ,('imagedeployments','Images'))
549 def get_form(self, request, obj=None, **kwargs):
550 if request.user.isReadOnlyUser():
551 kwargs["form"] = DeploymentAdminROForm
553 kwargs["form"] = DeploymentAdminForm
554 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
556 # from stackexchange: pass the request object into the form
558 class AdminFormMetaClass(adminForm):
559 def __new__(cls, *args, **kwargs):
560 kwargs['request'] = request
561 return adminForm(*args, **kwargs)
563 return AdminFormMetaClass
565 class ServiceAttrAsTabInline(PlStackTabularInline):
566 model = ServiceAttribute
567 fields = ['name','value']
569 suit_classes = 'suit-tab suit-tab-serviceattrs'
571 class ServiceAdmin(PlanetStackBaseAdmin):
572 list_display = ("backend_status_icon","name","description","versionNumber","enabled","published")
573 list_display_links = ('backend_status_icon', 'name', )
574 fieldList = ["backend_status_text","name","description","versionNumber","enabled","published"]
575 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
576 inlines = [ServiceAttrAsTabInline,SliceInline]
577 readonly_fields = ('backend_status_text', )
579 user_readonly_fields = fieldList
581 suit_form_tabs =(('general', 'Service Details'),
583 ('serviceattrs','Additional Attributes'),
586 class SiteAdmin(PlanetStackBaseAdmin):
587 fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
589 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
590 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
592 suit_form_tabs =(('general', 'Site Details'),
594 ('siteprivileges','Privileges'),
595 ('deployments','Deployments'),
600 readonly_fields = ['backend_status_text', 'accountLink']
602 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
604 list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
605 list_display_links = ('backend_status_icon', 'name', )
606 filter_horizontal = ('deployments',)
607 inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentInline]
608 search_fields = ['name']
610 def queryset(self, request):
611 return Site.select_by_user(request.user)
613 def get_formsets(self, request, obj=None):
614 for inline in self.get_inline_instances(request, obj):
615 # hide MyInline in the add view
618 if isinstance(inline, SliceInline):
619 inline.model.caller = request.user
620 yield inline.get_formset(request, obj)
622 def get_formsets(self, request, obj=None):
623 for inline in self.get_inline_instances(request, obj):
624 # hide MyInline in the add view
627 if isinstance(inline, SliverInline):
628 inline.model.caller = request.user
629 yield inline.get_formset(request, obj)
631 def accountLink(self, obj):
632 link_obj = obj.accounts.all()
634 reverse_path = "admin:core_account_change"
635 url = reverse(reverse_path, args =(link_obj[0].id,))
636 return "<a href='%s'>%s</a>" % (url, "view billing details")
638 return "no billing data for this site"
639 accountLink.allow_tags = True
640 accountLink.short_description = "Billing"
642 def save_model(self, request, obj, form, change):
643 # update openstack connection to use this site/tenant
644 obj.save_by_user(request.user)
646 def delete_model(self, request, obj):
647 obj.delete_by_user(request.user)
650 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
651 fieldList = ['backend_status_text', 'user', 'site', 'role']
653 (None, {'fields': fieldList, 'classes':['collapse']})
655 readonly_fields = ('backend_status_text', )
656 list_display = ('backend_status_icon', 'user', 'site', 'role')
657 list_display_links = list_display
658 user_readonly_fields = fieldList
659 user_readonly_inlines = []
661 def formfield_for_foreignkey(self, db_field, request, **kwargs):
662 if db_field.name == 'site':
663 if not request.user.is_admin:
664 # only show sites where user is an admin or pi
666 for site_privilege in SitePrivilege.objects.filer(user=request.user):
667 if site_privilege.role.role_type in ['admin', 'pi']:
668 sites.add(site_privilege.site)
669 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
671 if db_field.name == 'user':
672 if not request.user.is_admin:
673 # only show users from sites where caller has admin or pi role
674 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
675 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
676 sites = [site_privilege.site for site_privilege in site_privileges]
677 site_privileges = SitePrivilege.objects.filter(site__in=sites)
678 emails = [site_privilege.user.email for site_privilege in site_privileges]
679 users = User.objects.filter(email__in=emails)
680 kwargs['queryset'] = users
682 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
684 def queryset(self, request):
685 # admins can see all privileges. Users can only see privileges at sites
686 # where they have the admin role or pi role.
687 qs = super(SitePrivilegeAdmin, self).queryset(request)
688 #if not request.user.is_admin:
689 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
690 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
691 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
692 # sites = Site.objects.filter(login_base__in=login_bases)
693 # qs = qs.filter(site__in=sites)
696 class SliceForm(forms.ModelForm):
700 'service': LinkedSelect
703 class SliceAdmin(PlanetStackBaseAdmin):
705 fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
706 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
707 readonly_fields = ('backend_status_text', )
708 list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_slivers')
709 list_display_links = ('backend_status_icon', 'name', )
710 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
712 user_readonly_fields = fieldList
714 suit_form_tabs =(('general', 'Slice Details'),
715 ('slicenetworks','Networks'),
716 ('sliceprivileges','Privileges'),
717 ('slivers','Slivers'),
719 ('reservations','Reservations'),
722 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
723 deployment_nodes = []
724 for node in Node.objects.all():
725 deployment_nodes.append( (node.deployment.id, node.id, node.name) )
727 deployment_flavors = []
728 for flavor in Flavor.objects.all():
729 for deployment in flavor.deployments.all():
730 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
732 deployment_images = []
733 for image in Image.objects.all():
734 for imageDeployment in image.imagedeployments_set.all():
735 deployment_images.append( (imageDeployment.deployment.id, image.id, image.name) )
737 site_login_bases = []
738 for site in Site.objects.all():
739 site_login_bases.append((site.id, site.login_base))
741 context["deployment_nodes"] = deployment_nodes
742 context["deployment_flavors"] = deployment_flavors
743 context["deployment_images"] = deployment_images
744 context["site_login_bases"] = site_login_bases
745 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
747 def formfield_for_foreignkey(self, db_field, request, **kwargs):
748 if db_field.name == 'site':
749 kwargs['queryset'] = Site.select_by_user(request.user)
750 kwargs['widget'] = forms.Select(attrs={'onChange': "update_slice_prefix(this, $($(this).closest('fieldset')[0]).find('.field-name input')[0].id)"})
752 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
754 def queryset(self, request):
755 # admins can see all keys. Users can only see slices they belong to.
756 return Slice.select_by_user(request.user)
758 def get_formsets(self, request, obj=None):
759 for inline in self.get_inline_instances(request, obj):
760 # hide MyInline in the add view
763 if isinstance(inline, SliverInline):
764 inline.model.caller = request.user
765 yield inline.get_formset(request, obj)
768 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
770 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
772 readonly_fields = ('backend_status_text', )
773 list_display = ('backend_status_icon', 'user', 'slice', 'role')
774 list_display_links = list_display
776 user_readonly_fields = ['user', 'slice', 'role']
777 user_readonly_inlines = []
779 def formfield_for_foreignkey(self, db_field, request, **kwargs):
780 if db_field.name == 'slice':
781 kwargs['queryset'] = Slice.select_by_user(request.user)
783 if db_field.name == 'user':
784 kwargs['queryset'] = User.select_by_user(request.user)
786 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
788 def queryset(self, request):
789 # admins can see all memberships. Users can only see memberships of
790 # slices where they have the admin role.
791 return SlicePrivilege.select_by_user(request.user)
793 def save_model(self, request, obj, form, change):
794 # update openstack connection to use this site/tenant
795 auth = request.session.get('auth', {})
796 auth['tenant'] = obj.slice.slicename
797 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
800 def delete_model(self, request, obj):
801 # update openstack connection to use this site/tenant
802 auth = request.session.get('auth', {})
803 auth['tenant'] = obj.slice.slicename
804 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
808 class ImageAdmin(PlanetStackBaseAdmin):
810 fieldsets = [('Image Details',
811 {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
812 'classes': ['suit-tab suit-tab-general']})
814 readonly_fields = ('backend_status_text', )
816 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
818 inlines = [SliverInline, ImageDeploymentsInline]
820 user_readonly_fields = ['name', 'disk_format', 'container_format']
822 list_display = ['backend_status_icon', 'name']
823 list_display_links = ('backend_status_icon', 'name', )
825 class NodeForm(forms.ModelForm):
828 'site': LinkedSelect,
829 'deployment': LinkedSelect
832 class NodeAdmin(PlanetStackBaseAdmin):
834 list_display = ('backend_status_icon', 'name', 'site', 'deployment')
835 list_display_links = ('backend_status_icon', 'name', )
836 list_filter = ('deployment',)
838 inlines = [TagInline,SliverInline]
839 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
840 readonly_fields = ('backend_status_text', )
842 user_readonly_fields = ['name','site','deployment']
843 user_readonly_inlines = [TagInline,SliverInline]
845 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
848 class SliverForm(forms.ModelForm):
851 ip = forms.CharField(widget=PlainTextWidget)
852 instance_name = forms.CharField(widget=PlainTextWidget)
854 'ip': PlainTextWidget(),
855 'instance_name': PlainTextWidget(),
856 'slice': LinkedSelect,
857 'deploymentNetwork': LinkedSelect,
858 'node': LinkedSelect,
859 'image': LinkedSelect
862 class TagAdmin(PlanetStackBaseAdmin):
863 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
864 list_display_links = list_display
865 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
866 user_readonly_inlines = []
868 class SliverAdmin(PlanetStackBaseAdmin):
871 ('Sliver Details', {'fields': ['backend_status_text', 'slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
873 readonly_fields = ('backend_status_text', )
874 list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'flavor', 'image', 'node', 'deploymentNetwork']
875 list_display_links = ('backend_status_icon', 'ip',)
877 suit_form_tabs =(('general', 'Sliver Details'),
881 inlines = [TagInline]
883 user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image']
885 def formfield_for_foreignkey(self, db_field, request, **kwargs):
886 if db_field.name == 'slice':
887 kwargs['queryset'] = Slice.select_by_user(request.user)
889 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
891 def queryset(self, request):
892 # admins can see all slivers. Users can only see slivers of
893 # the slices they belong to.
894 return Sliver.select_by_user(request.user)
897 def get_formsets(self, request, obj=None):
898 # make some fields read only if we are updating an existing record
900 #self.readonly_fields = ('ip', 'instance_name')
901 self.readonly_fields = ('backend_status_text')
903 self.readonly_fields = ('backend_status_text')
904 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
906 for inline in self.get_inline_instances(request, obj):
907 # hide MyInline in the add view
910 if isinstance(inline, SliverInline):
911 inline.model.caller = request.user
912 yield inline.get_formset(request, obj)
914 #def save_model(self, request, obj, form, change):
915 # # update openstack connection to use this site/tenant
916 # auth = request.session.get('auth', {})
917 # auth['tenant'] = obj.slice.name
918 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
919 # obj.creator = request.user
922 #def delete_model(self, request, obj):
923 # # update openstack connection to use this site/tenant
924 # auth = request.session.get('auth', {})
925 # auth['tenant'] = obj.slice.name
926 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
929 class UserCreationForm(forms.ModelForm):
930 """A form for creating new users. Includes all the required
931 fields, plus a repeated password."""
932 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
933 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
937 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
939 def clean_password2(self):
940 # Check that the two password entries match
941 password1 = self.cleaned_data.get("password1")
942 password2 = self.cleaned_data.get("password2")
943 if password1 and password2 and password1 != password2:
944 raise forms.ValidationError("Passwords don't match")
947 def save(self, commit=True):
948 # Save the provided password in hashed format
949 user = super(UserCreationForm, self).save(commit=False)
950 user.password = self.cleaned_data["password1"]
951 #user.set_password(self.cleaned_data["password1"])
957 class UserChangeForm(forms.ModelForm):
958 """A form for updating users. Includes all the fields on
959 the user, but replaces the password field with admin's
960 password hash display field.
962 password = ReadOnlyPasswordHashField(label='Password',
963 help_text= '<a href=\"password/\">Change Password</a>.')
968 def clean_password(self):
969 # Regardless of what the user provides, return the initial value.
970 # This is done here, rather than on the field, because the
971 # field does not have access to the initial value
972 return self.initial["password"]
974 class UserDashboardViewInline(PlStackTabularInline):
975 model = UserDashboardView
977 suit_classes = 'suit-tab suit-tab-dashboards'
978 fields = ['user', 'dashboardView', 'order']
980 class UserAdmin(UserAdmin):
984 # The forms to add and change user instances
985 form = UserChangeForm
986 add_form = UserCreationForm
988 # The fields to be used in displaying the User model.
989 # These override the definitions on the base UserAdmin
990 # that reference specific fields on auth.User.
991 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
992 list_filter = ('site',)
993 inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
995 fieldListLoginDetails = ['email','site','password','is_active','is_readonly','is_admin','public_key']
996 fieldListContactInfo = ['firstname','lastname','phone','timezone']
999 ('Login Details', {'fields': ['backend_status_text', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1000 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1001 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1002 #('Important dates', {'fields': ('last_login',)}),
1006 'classes': ('wide',),
1007 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
1010 readonly_fields = ('backend_status_text', )
1011 search_fields = ('email',)
1012 ordering = ('email',)
1013 filter_horizontal = ()
1015 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1017 suit_form_tabs =(('general','Login Details'),
1018 ('contact','Contact Information'),
1019 ('sliceprivileges','Slice Privileges'),
1020 ('siteprivileges','Site Privileges'),
1021 ('deploymentprivileges','Deployment Privileges'),
1022 ('dashboards','Dashboard Views'))
1024 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1025 if db_field.name == 'site':
1026 kwargs['queryset'] = Site.select_by_user(request.user)
1028 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1030 def has_add_permission(self, request, obj=None):
1031 return (not self.__user_is_readonly(request))
1033 def has_delete_permission(self, request, obj=None):
1034 return (not self.__user_is_readonly(request))
1036 def get_actions(self,request):
1037 actions = super(UserAdmin,self).get_actions(request)
1039 if self.__user_is_readonly(request):
1040 if 'delete_selected' in actions:
1041 del actions['delete_selected']
1045 def change_view(self,request,object_id, extra_context=None):
1047 if self.__user_is_readonly(request):
1048 if not hasattr(self, "readonly_save"):
1049 # save the original readonly fields
\r
1050 self.readonly_save = self.readonly_fields
\r
1051 self.inlines_save = self.inlines
1052 if hasattr(self, "user_readonly_fields"):
1053 self.readonly_fields=self.user_readonly_fields
1054 if hasattr(self, "user_readonly_inlines"):
1055 self.inlines = self.user_readonly_inlines
1057 if hasattr(self, "readonly_save"):
\r
1058 # restore the original readonly fields
\r
1059 self.readonly_fields = self.readonly_save
\r
1060 self.inlines = self.inlines_save
1063 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1064 except PermissionDenied:
1066 if request.method == 'POST':
1067 raise PermissionDenied
1068 request.readonly = True
1069 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1071 def __user_is_readonly(self, request):
1072 #groups = [x.name for x in request.user.groups.all() ]
1073 #return "readonly" in groups
1074 return request.user.isReadOnlyUser()
1076 def queryset(self, request):
1077 return User.select_by_user(request.user)
1079 def backend_status_text(self, obj):
1080 return mark_safe(backend_text(obj))
1082 def backend_status_icon(self, obj):
1083 return mark_safe(backend_icon(obj))
1084 backend_status_icon.short_description = ""
1086 class DashboardViewAdmin(PlanetStackBaseAdmin):
1087 fieldsets = [('Dashboard View Details',
1088 {'fields': ['backend_status_text', 'name', 'url'],
1089 'classes': ['suit-tab suit-tab-general']})
1091 readonly_fields = ('backend_status_text', )
1093 suit_form_tabs =(('general','Dashboard View Details'),)
1095 class ServiceResourceInline(PlStackTabularInline):
1096 model = ServiceResource
1099 class ServiceClassAdmin(PlanetStackBaseAdmin):
1100 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1101 list_display_links = ('backend_status_icon', 'name', )
1102 inlines = [ServiceResourceInline]
1104 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1105 user_readonly_inlines = []
1107 class ReservedResourceInline(PlStackTabularInline):
1108 model = ReservedResource
1110 suit_classes = 'suit-tab suit-tab-reservedresources'
1112 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1113 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1115 if db_field.name == 'resource':
1116 # restrict resources to those that the slice's service class allows
1117 if request._slice is not None:
1118 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1119 if len(field.queryset) > 0:
1120 field.initial = field.queryset.all()[0]
1122 field.queryset = field.queryset.none()
\r
1123 elif db_field.name == 'sliver':
\r
1124 # restrict slivers to those that belong to the slice
\r
1125 if request._slice is not None:
\r
1126 field.queryset = field.queryset.filter(slice = request._slice)
1128 field.queryset = field.queryset.none()
\r
1132 def queryset(self, request):
1133 return ReservedResource.select_by_user(request.user)
1135 class ReservationChangeForm(forms.ModelForm):
1139 'slice' : LinkedSelect
1142 class ReservationAddForm(forms.ModelForm):
1143 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1144 refresh = forms.CharField(widget=forms.HiddenInput())
1147 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1149 def clean_slice(self):
1150 slice = self.cleaned_data.get("slice")
1151 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1153 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1159 'slice' : LinkedSelect
1163 class ReservationAddRefreshForm(ReservationAddForm):
1164 """ This form is displayed when the Reservation Form receives an update
1165 from the Slice dropdown onChange handler. It doesn't validate the
1166 data and doesn't save the data. This will cause the form to be
1170 """ don't validate anything other than slice """
1171 dont_validate_fields = ("startTime", "duration")
1173 def full_clean(self):
1174 result = super(ReservationAddForm, self).full_clean()
1176 for fieldname in self.dont_validate_fields:
1177 if fieldname in self._errors:
1178 del self._errors[fieldname]
1182 """ don't save anything """
1186 class ReservationAdmin(PlanetStackBaseAdmin):
1187 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
1188 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1189 readonly_fields = ('backend_status_text', )
1190 list_display = ('startTime', 'duration')
1191 form = ReservationAddForm
1193 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1195 inlines = [ReservedResourceInline]
1196 user_readonly_fields = fieldList
1198 def add_view(self, request, form_url='', extra_context=None):
1199 timezone.activate(request.user.timezone)
1200 request._refresh = False
1201 request._slice = None
1202 if request.method == 'POST':
1203 # "refresh" will be set to "1" if the form was submitted due to
1204 # a change in the Slice dropdown.
1205 if request.POST.get("refresh","1") == "1":
1206 request._refresh = True
1207 request.POST["refresh"] = "0"
1209 # Keep track of the slice that was selected, so the
1210 # reservedResource inline can filter items for the slice.
1211 request._slice = request.POST.get("slice",None)
1212 if (request._slice is not None):
1213 request._slice = Slice.objects.get(id=request._slice)
1215 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1218 def changelist_view(self, request, extra_context = None):
1219 timezone.activate(request.user.timezone)
1220 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1222 def get_form(self, request, obj=None, **kwargs):
1225 # For changes, set request._slice to the slice already set in the
1227 request._slice = obj.slice
1228 self.form = ReservationChangeForm
1230 if getattr(request, "_refresh", False):
1231 self.form = ReservationAddRefreshForm
1233 self.form = ReservationAddForm
1234 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1236 def get_readonly_fields(self, request, obj=None):
1237 if (obj is not None):
1238 # Prevent slice from being changed after the reservation has been
1244 def queryset(self, request):
1245 return Reservation.select_by_user(request.user)
1247 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1248 list_display = ("backend_status_icon", "name", )
1249 list_display_links = ('backend_status_icon', 'name', )
1250 user_readonly_fields = ['name']
1251 user_readonly_inlines = []
1253 class RouterAdmin(PlanetStackBaseAdmin):
1254 list_display = ("backend_status_icon", "name", )
1255 list_display_links = ('backend_status_icon', 'name', )
1256 user_readonly_fields = ['name']
1257 user_readonly_inlines = []
1259 class RouterInline(PlStackTabularInline):
1260 model = Router.networks.through
1262 verbose_name_plural = "Routers"
1263 verbose_name = "Router"
1264 suit_classes = 'suit-tab suit-tab-routers'
1266 class NetworkParameterInline(PlStackGenericTabularInline):
1267 model = NetworkParameter
1269 verbose_name_plural = "Parameters"
1270 verbose_name = "Parameter"
1271 suit_classes = 'suit-tab suit-tab-netparams'
1272 fields = ['backend_status_icon', 'parameter', 'value']
1273 readonly_fields = ('backend_status_icon', )
1275 class NetworkSliversInline(PlStackTabularInline):
1276 fields = ['backend_status_icon', 'network','sliver','ip']
1277 readonly_fields = ("backend_status_icon", "ip", )
1278 model = NetworkSliver
1279 selflink_fieldname = "sliver"
1281 verbose_name_plural = "Slivers"
1282 verbose_name = "Sliver"
1283 suit_classes = 'suit-tab suit-tab-networkslivers'
1285 class NetworkSlicesInline(PlStackTabularInline):
1286 model = NetworkSlice
1287 selflink_fieldname = "slice"
1289 verbose_name_plural = "Slices"
1290 verbose_name = "Slice"
1291 suit_classes = 'suit-tab suit-tab-networkslices'
1292 fields = ['backend_status_icon', 'network','slice']
1293 readonly_fields = ('backend_status_icon', )
1295 class NetworkAdmin(PlanetStackBaseAdmin):
1296 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1297 list_display_links = ('backend_status_icon', 'name', )
1298 readonly_fields = ("subnet", )
1300 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1303 (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']}),]
1305 readonly_fields = ('backend_status_text', )
1306 user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1309 ('general','Network Details'),
1310 ('netparams', 'Parameters'),
1311 ('networkslivers','Slivers'),
1312 ('networkslices','Slices'),
1313 ('routers','Routers'),
1315 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1316 list_display = ("backend_status_icon", "name", "guaranteedBandwidth", "visibility")
1317 list_display_links = ('backend_status_icon', 'name', )
1318 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1319 user_readonly_inlines = []
1321 class FlavorAdmin(PlanetStackBaseAdmin):
1322 list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1323 list_display_links = ("backend_status_icon", "name")
1324 user_readonly_fields = ("name", "flavor")
1325 fields = ("name", "description", "flavor", "order", "default")
1327 # register a signal that caches the user's credentials when they log in
1328 def cache_credentials(sender, user, request, **kwds):
1329 auth = {'username': request.POST['username'],
1330 'password': request.POST['password']}
1331 request.session['auth'] = auth
1332 user_logged_in.connect(cache_credentials)
1334 def dollar_field(fieldName, short_description):
1335 def newFunc(self, obj):
1337 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1339 x=getattr(obj, fieldName, 0.0)
1341 newFunc.short_description = short_description
1344 def right_dollar_field(fieldName, short_description):
1345 def newFunc(self, obj):
1347 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1348 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1350 x=getattr(obj, fieldName, 0.0)
1352 newFunc.short_description = short_description
1353 newFunc.allow_tags = True
1356 class InvoiceChargeInline(PlStackTabularInline):
1359 verbose_name_plural = "Charges"
1360 verbose_name = "Charge"
1361 exclude = ['account']
1362 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1363 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1367 dollar_amount = right_dollar_field("amount", "Amount")
1369 class InvoiceAdmin(admin.ModelAdmin):
1370 list_display = ("date", "account")
1372 inlines = [InvoiceChargeInline]
1374 fields = ["date", "account", "dollar_amount"]
1375 readonly_fields = ["date", "account", "dollar_amount"]
1377 dollar_amount = dollar_field("amount", "Amount")
1379 class InvoiceInline(PlStackTabularInline):
1382 verbose_name_plural = "Invoices"
1383 verbose_name = "Invoice"
1384 fields = ["date", "dollar_amount"]
1385 readonly_fields = ["date", "dollar_amount"]
1386 suit_classes = 'suit-tab suit-tab-accountinvoice'
1390 dollar_amount = right_dollar_field("amount", "Amount")
1392 class PendingChargeInline(PlStackTabularInline):
1395 verbose_name_plural = "Charges"
1396 verbose_name = "Charge"
1397 exclude = ["invoice"]
1398 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1399 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1400 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1404 def queryset(self, request):
1405 qs = super(PendingChargeInline, self).queryset(request)
1406 qs = qs.filter(state="pending")
1409 dollar_amount = right_dollar_field("amount", "Amount")
1411 class PaymentInline(PlStackTabularInline):
1414 verbose_name_plural = "Payments"
1415 verbose_name = "Payment"
1416 fields = ["date", "dollar_amount"]
1417 readonly_fields = ["date", "dollar_amount"]
1418 suit_classes = 'suit-tab suit-tab-accountpayments'
1422 dollar_amount = right_dollar_field("amount", "Amount")
1424 class AccountAdmin(admin.ModelAdmin):
1425 list_display = ("site", "balance_due")
1427 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1430 (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1432 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1435 ('general','Account Details'),
1436 ('accountinvoice', 'Invoices'),
1437 ('accountpayments', 'Payments'),
1438 ('accountpendingcharges','Pending Charges'),
1441 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1442 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1443 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1445 # Now register the new UserAdmin...
1446 admin.site.register(User, UserAdmin)
1447 # ... and, since we're not using Django's builtin permissions,
1448 # unregister the Group model from admin.
1449 #admin.site.unregister(Group)
1451 #Do not show django evolution in the admin interface
1452 from django_evolution.models import Version, Evolution
1453 #admin.site.unregister(Version)
1454 #admin.site.unregister(Evolution)
1457 # When debugging it is often easier to see all the classes, but for regular use
1458 # only the top-levels should be displayed
1461 admin.site.register(Deployment, DeploymentAdmin)
1462 admin.site.register(Site, SiteAdmin)
1463 admin.site.register(Slice, SliceAdmin)
1464 admin.site.register(Service, ServiceAdmin)
1465 admin.site.register(Reservation, ReservationAdmin)
1466 admin.site.register(Network, NetworkAdmin)
1467 admin.site.register(Router, RouterAdmin)
1468 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1469 admin.site.register(Account, AccountAdmin)
1470 admin.site.register(Invoice, InvoiceAdmin)
1473 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1474 admin.site.register(ServiceClass, ServiceClassAdmin)
1475 #admin.site.register(PlanetStack)
1476 admin.site.register(Tag, TagAdmin)
1477 admin.site.register(DeploymentRole)
1478 admin.site.register(SiteRole)
1479 admin.site.register(SliceRole)
1480 admin.site.register(PlanetStackRole)
1481 admin.site.register(Node, NodeAdmin)
1482 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1483 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1484 admin.site.register(Sliver, SliverAdmin)
1485 admin.site.register(Image, ImageAdmin)
1486 admin.site.register(DashboardView, DashboardViewAdmin)
1487 admin.site.register(Flavor, FlavorAdmin)