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 '<img src="/static/admin/img/icon_success.gif">'
26 if obj.backend_status == "Provisioning in progress" or obj.backend_status=="":
27 return '<div title="%s"><img src="/static/admin/img/icon_clock.gif"></div>' % obj.backend_status
29 return '<div title="%s"><img src="/static/admin/img/icon_error.gif"></div>' % 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', 'numberCores', 'deploymentNetwork', '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 # the inscrutable jquery selector below says:
281 # find the closest parent "tr" to the current element
282 # then find the child with class "field-node"
283 # then find the child with that is a select
285 kwargs['widget'] = forms.Select(attrs={'onChange': "update_nodes(this, $($(this).closest('tr')[0]).find('.field-node select')[0].id)"})
286 #kwargs['widget'] = forms.Select(attrs={'onChange': "console.log($($($(this).closest('tr')[0]).children('.field-node')[0]).children('select')[0].id);"})
288 field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
293 SMBAKER: This is the old code that implemented each network type as a
294 separate column in the sliver table.
296 def _declared_fieldsets(self):
297 # Return None so django will call get_fieldsets and we can insert our
301 def get_readonly_fields(self, request, obj=None):
302 readonly_fields = list(super(SliverInline, self).get_readonly_fields(request, obj))
304 # Lookup the networks that are bound to the slivers, and add those
305 # network names to the list of readonly fields.
307 for sliver in obj.slivers.all():
308 for nbs in sliver.networksliver_set.all():
310 network_name = nbs.network.name
311 if network_name not in [str(x) for x in readonly_fields]:
312 readonly_fields.append(NetworkLookerUpper.get(network_name))
314 return readonly_fields
316 def get_fieldsets(self, request, obj=None):
317 form = self.get_formset(request, obj).form
318 # fields = the read/write files + the read-only fields
319 fields = list(self.fields)
320 for fieldName in self.get_readonly_fields(request,obj):
321 if not fieldName in fields:
322 fields.append(fieldName)
324 return [(None, {'fields': fields})]
327 class SiteInline(PlStackTabularInline):
330 suit_classes = 'suit-tab suit-tab-sites'
332 def queryset(self, request):
333 return Site.select_by_user(request.user)
335 class UserInline(PlStackTabularInline):
337 fields = ['backend_status_icon', 'email', 'firstname', 'lastname']
338 readonly_fields = ('backend_status_icon', )
340 suit_classes = 'suit-tab suit-tab-users'
342 def queryset(self, request):
343 return User.select_by_user(request.user)
345 class SliceInline(PlStackTabularInline):
347 fields = ['backend_status_icon', 'name', 'site', 'serviceClass', 'service']
348 readonly_fields = ('backend_status_icon', )
350 suit_classes = 'suit-tab suit-tab-slices'
352 def queryset(self, request):
353 return Slice.select_by_user(request.user)
355 class NodeInline(PlStackTabularInline):
358 suit_classes = 'suit-tab suit-tab-nodes'
359 fields = ['backend_status_icon', 'name','deployment','site']
360 readonly_fields = ('backend_status_icon', )
362 class DeploymentPrivilegeInline(PlStackTabularInline):
363 model = DeploymentPrivilege
365 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
366 fields = ['backend_status_icon', 'user','role','deployment']
367 readonly_fields = ('backend_status_icon', )
369 def queryset(self, request):
370 return DeploymentPrivilege.select_by_user(request.user)
372 class SitePrivilegeInline(PlStackTabularInline):
373 model = SitePrivilege
375 suit_classes = 'suit-tab suit-tab-siteprivileges'
376 fields = ['backend_status_icon', 'user','site', 'role']
377 readonly_fields = ('backend_status_icon', )
379 def formfield_for_foreignkey(self, db_field, request, **kwargs):
380 if db_field.name == 'site':
381 kwargs['queryset'] = Site.select_by_user(request.user)
383 if db_field.name == 'user':
384 kwargs['queryset'] = User.select_by_user(request.user)
385 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
387 def queryset(self, request):
388 return SitePrivilege.select_by_user(request.user)
390 class SiteDeploymentInline(PlStackTabularInline):
391 model = SiteDeployments
393 suit_classes = 'suit-tab suit-tab-deployments'
394 fields = ['backend_status_icon', 'deployment','site']
395 readonly_fields = ('backend_status_icon', )
397 def formfield_for_foreignkey(self, db_field, request, **kwargs):
398 if db_field.name == 'site':
399 kwargs['queryset'] = Site.select_by_user(request.user)
401 if db_field.name == 'deployment':
402 kwargs['queryset'] = Deployment.select_by_user(request.user)
403 return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
405 def queryset(self, request):
406 return SiteDeployments.select_by_user(request.user)
409 class SlicePrivilegeInline(PlStackTabularInline):
410 model = SlicePrivilege
411 suit_classes = 'suit-tab suit-tab-sliceprivileges'
413 fields = ('backend_status_icon', 'user', 'slice', 'role')
414 readonly_fields = ('backend_status_icon', )
416 def formfield_for_foreignkey(self, db_field, request, **kwargs):
417 if db_field.name == 'slice':
418 kwargs['queryset'] = Slice.select_by_user(request.user)
419 if db_field.name == 'user':
420 kwargs['queryset'] = User.select_by_user(request.user)
422 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
424 def queryset(self, request):
425 return SlicePrivilege.select_by_user(request.user)
427 class SliceNetworkInline(PlStackTabularInline):
428 model = Network.slices.through
429 selflink_fieldname = "network"
431 verbose_name = "Network Connection"
432 verbose_name_plural = "Network Connections"
433 suit_classes = 'suit-tab suit-tab-slicenetworks'
434 fields = ['backend_status_icon', 'network']
435 readonly_fields = ('backend_status_icon', )
437 class ImageDeploymentsInline(PlStackTabularInline):
438 model = ImageDeployments
440 verbose_name = "Image Deployments"
441 verbose_name_plural = "Image Deployments"
442 suit_classes = 'suit-tab suit-tab-imagedeployments'
443 fields = ['backend_status_icon', 'image', 'deployment', 'glance_image_id']
444 readonly_fields = ['backend_status_icon', 'glance_image_id']
446 class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
449 def save_model(self, request, obj, form, change):
450 obj.caller = request.user
451 # update openstack connection to use this site/tenant
452 obj.save_by_user(request.user)
454 def delete_model(self, request, obj):
455 obj.delete_by_user(request.user)
457 def save_formset(self, request, form, formset, change):
458 instances = formset.save(commit=False)
459 for instance in instances:
460 instance.save_by_user(request.user)
463 class SliceRoleAdmin(PlanetStackBaseAdmin):
467 class SiteRoleAdmin(PlanetStackBaseAdmin):
471 class DeploymentAdminForm(forms.ModelForm):
472 sites = forms.ModelMultipleChoiceField(
473 queryset=Site.objects.all(),
475 help_text="Select which sites are allowed to host nodes in this deployment",
476 widget=FilteredSelectMultiple(
477 verbose_name=('Sites'), is_stacked=False
480 images = forms.ModelMultipleChoiceField(
481 queryset=Image.objects.all(),
483 help_text="Select which images should be deployed on this deployment",
484 widget=FilteredSelectMultiple(
485 verbose_name=('Images'), is_stacked=False
491 def __init__(self, *args, **kwargs):
492 request = kwargs.pop('request', None)
493 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
495 self.fields['accessControl'].initial = "allow site " + request.user.site.name
497 if self.instance and self.instance.pk:
498 self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments_set.all()]
499 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments_set.all()]
501 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
502 """ helper function for handling m2m relations from the MultipleChoiceField
504 this_obj: the source object we want to link from
506 selected_objs: a list of destination objects we want to link to
508 all_relations: the full set of relations involving this_obj, including ones we don't want
510 relation_class: the class that implements the relation from source to dest
512 local_attrname: field name representing this_obj in relation_class
514 foreign_attrname: field name representing selected_objs in relation_class
516 This function will remove all newobjclass relations from this_obj
517 that are not contained in selected_objs, and add any relations that
518 are in selected_objs but don't exist in the data model yet.
521 existing_dest_objs = []
522 for relation in list(all_relations):
523 if getattr(relation, foreign_attrname) not in selected_objs:
524 #print "deleting site", sdp.site
527 existing_dest_objs.append(getattr(relation, foreign_attrname))
529 for dest_obj in selected_objs:
530 if dest_obj not in existing_dest_objs:
531 #print "adding site", site
532 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
533 relation = relation_class(**kwargs)
536 def save(self, commit=True):
537 deployment = super(DeploymentAdminForm, self).save(commit=False)
543 # save_m2m() doesn't seem to work with 'through' relations. So we
544 # create/destroy the through models ourselves. There has to be
547 self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments_set.all(), SiteDeployments, "deployment", "site")
548 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments_set.all(), ImageDeployments, "deployment", "image")
554 class DeploymentAdminROForm(DeploymentAdminForm):
555 def save(self, commit=True):
556 raise PermissionDenied
558 class SiteAssocInline(PlStackTabularInline):
559 model = Site.deployments.through
561 suit_classes = 'suit-tab suit-tab-sites'
563 class DeploymentAdmin(PlanetStackBaseAdmin):
565 fieldList = ['backend_status_text', 'name', 'sites', 'images', 'accessControl']
566 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
567 inlines = [DeploymentPrivilegeInline,NodeInline,TagInline] # ,ImageDeploymentsInline]
568 list_display = ['backend_status_icon', 'name']
569 list_display_links = ('backend_status_icon', 'name', )
570 readonly_fields = ('backend_status_text', )
572 user_readonly_fields = ['name']
574 suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags')) # ,('imagedeployments','Images'))
576 def get_form(self, request, obj=None, **kwargs):
577 if request.user.isReadOnlyUser():
578 kwargs["form"] = DeploymentAdminROForm
580 kwargs["form"] = DeploymentAdminForm
581 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
583 # from stackexchange: pass the request object into the form
585 class AdminFormMetaClass(adminForm):
586 def __new__(cls, *args, **kwargs):
587 kwargs['request'] = request
588 return adminForm(*args, **kwargs)
590 return AdminFormMetaClass
592 class ServiceAttrAsTabInline(PlStackTabularInline):
593 model = ServiceAttribute
594 fields = ['name','value']
596 suit_classes = 'suit-tab suit-tab-serviceattrs'
598 class ServiceAdmin(PlanetStackBaseAdmin):
599 list_display = ("backend_status_icon","name","description","versionNumber","enabled","published")
600 list_display_links = ('backend_status_icon', 'name', )
601 fieldList = ["backend_status_text","name","description","versionNumber","enabled","published"]
602 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
603 inlines = [ServiceAttrAsTabInline,SliceInline]
604 readonly_fields = ('backend_status_text', )
606 user_readonly_fields = fieldList
608 suit_form_tabs =(('general', 'Service Details'),
610 ('serviceattrs','Additional Attributes'),
613 class SiteAdmin(PlanetStackBaseAdmin):
614 fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
616 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
617 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
619 suit_form_tabs =(('general', 'Site Details'),
621 ('siteprivileges','Privileges'),
622 ('deployments','Deployments'),
627 readonly_fields = ['backend_status_text', 'accountLink']
629 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
631 list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
632 list_display_links = ('backend_status_icon', 'name', )
633 filter_horizontal = ('deployments',)
634 inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentInline]
635 search_fields = ['name']
637 def queryset(self, request):
638 return Site.select_by_user(request.user)
640 def get_formsets(self, request, obj=None):
641 for inline in self.get_inline_instances(request, obj):
642 # hide MyInline in the add view
645 if isinstance(inline, SliceInline):
646 inline.model.caller = request.user
647 yield inline.get_formset(request, obj)
649 def get_formsets(self, request, obj=None):
650 for inline in self.get_inline_instances(request, obj):
651 # hide MyInline in the add view
654 if isinstance(inline, SliverInline):
655 inline.model.caller = request.user
656 yield inline.get_formset(request, obj)
658 def accountLink(self, obj):
659 link_obj = obj.accounts.all()
661 reverse_path = "admin:core_account_change"
662 url = reverse(reverse_path, args =(link_obj[0].id,))
663 return "<a href='%s'>%s</a>" % (url, "view billing details")
665 return "no billing data for this site"
666 accountLink.allow_tags = True
667 accountLink.short_description = "Billing"
669 def save_model(self, request, obj, form, change):
670 # update openstack connection to use this site/tenant
671 obj.save_by_user(request.user)
673 def delete_model(self, request, obj):
674 obj.delete_by_user(request.user)
677 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
678 fieldList = ['backend_status_text', 'user', 'site', 'role']
680 (None, {'fields': fieldList, 'classes':['collapse']})
682 readonly_fields = ('backend_status_text', )
683 list_display = ('backend_status_icon', 'user', 'site', 'role')
684 list_display_links = list_display
685 user_readonly_fields = fieldList
686 user_readonly_inlines = []
688 def formfield_for_foreignkey(self, db_field, request, **kwargs):
689 if db_field.name == 'site':
690 if not request.user.is_admin:
691 # only show sites where user is an admin or pi
693 for site_privilege in SitePrivilege.objects.filer(user=request.user):
694 if site_privilege.role.role_type in ['admin', 'pi']:
695 sites.add(site_privilege.site)
696 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
698 if db_field.name == 'user':
699 if not request.user.is_admin:
700 # only show users from sites where caller has admin or pi role
701 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
702 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
703 sites = [site_privilege.site for site_privilege in site_privileges]
704 site_privileges = SitePrivilege.objects.filter(site__in=sites)
705 emails = [site_privilege.user.email for site_privilege in site_privileges]
706 users = User.objects.filter(email__in=emails)
707 kwargs['queryset'] = users
709 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
711 def queryset(self, request):
712 # admins can see all privileges. Users can only see privileges at sites
713 # where they have the admin role or pi role.
714 qs = super(SitePrivilegeAdmin, self).queryset(request)
715 #if not request.user.is_admin:
716 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
717 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
718 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
719 # sites = Site.objects.filter(login_base__in=login_bases)
720 # qs = qs.filter(site__in=sites)
723 class SliceForm(forms.ModelForm):
727 'service': LinkedSelect
730 class SliceAdmin(PlanetStackBaseAdmin):
732 fieldList = ['backend_status_text', 'name', 'site', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
733 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
734 readonly_fields = ('backend_status_text', )
735 list_display = ('backend_status_icon', 'slicename', 'site','serviceClass', 'slice_url', 'max_slivers')
736 list_display_links = ('backend_status_icon', 'slicename', )
737 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
739 user_readonly_fields = fieldList
741 suit_form_tabs =(('general', 'Slice Details'),
742 ('slicenetworks','Networks'),
743 ('sliceprivileges','Privileges'),
744 ('slivers','Slivers'),
746 ('reservations','Reservations'),
749 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
750 #deployment_nodes = {}
751 #for node in Node.objects.all():
752 # deployment_nodes[node.deployment.id] = get(deployment_nodes, node.deployment.id, []).append( (node.id, node.name) )
754 deployment_nodes = []
755 for node in Node.objects.all():
756 deployment_nodes.append( (node.deployment.id, node.id, node.name) )
758 context["deployment_nodes"] = deployment_nodes
760 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
762 def formfield_for_foreignkey(self, db_field, request, **kwargs):
763 if db_field.name == 'site':
764 kwargs['queryset'] = Site.select_by_user(request.user)
766 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
768 def queryset(self, request):
769 # admins can see all keys. Users can only see slices they belong to.
770 return Slice.select_by_user(request.user)
772 def get_formsets(self, request, obj=None):
773 for inline in self.get_inline_instances(request, obj):
774 # hide MyInline in the add view
777 if isinstance(inline, SliverInline):
778 inline.model.caller = request.user
779 yield inline.get_formset(request, obj)
782 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
784 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
786 readonly_fields = ('backend_status_text', )
787 list_display = ('backend_status_icon', 'user', 'slice', 'role')
788 list_display_links = list_display
790 user_readonly_fields = ['user', 'slice', 'role']
791 user_readonly_inlines = []
793 def formfield_for_foreignkey(self, db_field, request, **kwargs):
794 if db_field.name == 'slice':
795 kwargs['queryset'] = Slice.select_by_user(request.user)
797 if db_field.name == 'user':
798 kwargs['queryset'] = User.select_by_user(request.user)
800 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
802 def queryset(self, request):
803 # admins can see all memberships. Users can only see memberships of
804 # slices where they have the admin role.
805 return SlicePrivilege.select_by_user(request.user)
807 def save_model(self, request, obj, form, change):
808 # update openstack connection to use this site/tenant
809 auth = request.session.get('auth', {})
810 auth['tenant'] = obj.slice.slicename
811 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
814 def delete_model(self, request, obj):
815 # update openstack connection to use this site/tenant
816 auth = request.session.get('auth', {})
817 auth['tenant'] = obj.slice.slicename
818 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
822 class ImageAdmin(PlanetStackBaseAdmin):
824 fieldsets = [('Image Details',
825 {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
826 'classes': ['suit-tab suit-tab-general']})
828 readonly_fields = ('backend_status_text', )
830 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
832 inlines = [SliverInline, ImageDeploymentsInline]
834 user_readonly_fields = ['name', 'disk_format', 'container_format']
836 list_display = ['backend_status_icon', 'name']
837 list_display_links = ('backend_status_icon', 'name', )
839 class NodeForm(forms.ModelForm):
842 'site': LinkedSelect,
843 'deployment': LinkedSelect
846 class NodeAdmin(PlanetStackBaseAdmin):
848 list_display = ('backend_status_icon', 'name', 'site', 'deployment')
849 list_display_links = ('backend_status_icon', 'name', )
850 list_filter = ('deployment',)
852 inlines = [TagInline,SliverInline]
853 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
854 readonly_fields = ('backend_status_text', )
856 user_readonly_fields = ['name','site','deployment']
857 user_readonly_inlines = [TagInline,SliverInline]
859 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
862 class SliverForm(forms.ModelForm):
865 ip = forms.CharField(widget=PlainTextWidget)
866 instance_name = forms.CharField(widget=PlainTextWidget)
868 'ip': PlainTextWidget(),
869 'instance_name': PlainTextWidget(),
870 'slice': LinkedSelect,
871 'deploymentNetwork': LinkedSelect,
872 'node': LinkedSelect,
873 'image': LinkedSelect
876 class TagAdmin(PlanetStackBaseAdmin):
877 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
878 list_display_links = list_display
879 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
880 user_readonly_inlines = []
882 class SliverAdmin(PlanetStackBaseAdmin):
885 ('Sliver Details', {'fields': ['backend_status_text', 'slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
887 readonly_fields = ('backend_status_text', )
888 list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']
889 list_display_links = ('backend_status_icon', 'ip',)
891 suit_form_tabs =(('general', 'Sliver Details'),
895 inlines = [TagInline]
897 user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image']
899 def formfield_for_foreignkey(self, db_field, request, **kwargs):
900 if db_field.name == 'slice':
901 kwargs['queryset'] = Slice.select_by_user(request.user)
903 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
905 def queryset(self, request):
906 # admins can see all slivers. Users can only see slivers of
907 # the slices they belong to.
908 return Sliver.select_by_user(request.user)
911 def get_formsets(self, request, obj=None):
912 # make some fields read only if we are updating an existing record
914 #self.readonly_fields = ('ip', 'instance_name')
915 self.readonly_fields = ('backend_status_text')
917 self.readonly_fields = ('backend_status_text')
918 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
920 for inline in self.get_inline_instances(request, obj):
921 # hide MyInline in the add view
924 if isinstance(inline, SliverInline):
925 inline.model.caller = request.user
926 yield inline.get_formset(request, obj)
928 #def save_model(self, request, obj, form, change):
929 # # update openstack connection to use this site/tenant
930 # auth = request.session.get('auth', {})
931 # auth['tenant'] = obj.slice.name
932 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
933 # obj.creator = request.user
936 #def delete_model(self, request, obj):
937 # # update openstack connection to use this site/tenant
938 # auth = request.session.get('auth', {})
939 # auth['tenant'] = obj.slice.name
940 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
943 class UserCreationForm(forms.ModelForm):
944 """A form for creating new users. Includes all the required
945 fields, plus a repeated password."""
946 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
947 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
951 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
953 def clean_password2(self):
954 # Check that the two password entries match
955 password1 = self.cleaned_data.get("password1")
956 password2 = self.cleaned_data.get("password2")
957 if password1 and password2 and password1 != password2:
958 raise forms.ValidationError("Passwords don't match")
961 def save(self, commit=True):
962 # Save the provided password in hashed format
963 user = super(UserCreationForm, self).save(commit=False)
964 user.password = self.cleaned_data["password1"]
965 #user.set_password(self.cleaned_data["password1"])
971 class UserChangeForm(forms.ModelForm):
972 """A form for updating users. Includes all the fields on
973 the user, but replaces the password field with admin's
974 password hash display field.
976 password = ReadOnlyPasswordHashField(label='Password',
977 help_text= '<a href=\"password/\">Change Password</a>.')
982 def clean_password(self):
983 # Regardless of what the user provides, return the initial value.
984 # This is done here, rather than on the field, because the
985 # field does not have access to the initial value
986 return self.initial["password"]
988 class UserDashboardViewInline(PlStackTabularInline):
989 model = UserDashboardView
991 suit_classes = 'suit-tab suit-tab-dashboards'
992 fields = ['user', 'dashboardView', 'order']
994 class UserAdmin(UserAdmin):
998 # The forms to add and change user instances
999 form = UserChangeForm
1000 add_form = UserCreationForm
1002 # The fields to be used in displaying the User model.
1003 # These override the definitions on the base UserAdmin
1004 # that reference specific fields on auth.User.
1005 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
1006 list_filter = ('site',)
1007 inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
1009 fieldListLoginDetails = ['email','site','password','is_active','is_readonly','is_admin','public_key']
1010 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1013 ('Login Details', {'fields': ['backend_status_text', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1014 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1015 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1016 #('Important dates', {'fields': ('last_login',)}),
1020 'classes': ('wide',),
1021 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
1024 readonly_fields = ('backend_status_text', )
1025 search_fields = ('email',)
1026 ordering = ('email',)
1027 filter_horizontal = ()
1029 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1031 suit_form_tabs =(('general','Login Details'),
1032 ('contact','Contact Information'),
1033 ('sliceprivileges','Slice Privileges'),
1034 ('siteprivileges','Site Privileges'),
1035 ('deploymentprivileges','Deployment Privileges'),
1036 ('dashboards','Dashboard Views'))
1038 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1039 if db_field.name == 'site':
1040 kwargs['queryset'] = Site.select_by_user(request.user)
1042 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1044 def has_add_permission(self, request, obj=None):
1045 return (not self.__user_is_readonly(request))
1047 def has_delete_permission(self, request, obj=None):
1048 return (not self.__user_is_readonly(request))
1050 def get_actions(self,request):
1051 actions = super(UserAdmin,self).get_actions(request)
1053 if self.__user_is_readonly(request):
1054 if 'delete_selected' in actions:
1055 del actions['delete_selected']
1059 def change_view(self,request,object_id, extra_context=None):
1061 if self.__user_is_readonly(request):
1062 if not hasattr(self, "readonly_save"):
1063 # save the original readonly fields
\r
1064 self.readonly_save = self.readonly_fields
\r
1065 self.inlines_save = self.inlines
1066 if hasattr(self, "user_readonly_fields"):
1067 self.readonly_fields=self.user_readonly_fields
1068 if hasattr(self, "user_readonly_inlines"):
1069 self.inlines = self.user_readonly_inlines
1071 if hasattr(self, "readonly_save"):
\r
1072 # restore the original readonly fields
\r
1073 self.readonly_fields = self.readonly_save
\r
1074 self.inlines = self.inlines_save
1077 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1078 except PermissionDenied:
1080 if request.method == 'POST':
1081 raise PermissionDenied
1082 request.readonly = True
1083 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1085 def __user_is_readonly(self, request):
1086 #groups = [x.name for x in request.user.groups.all() ]
1087 #return "readonly" in groups
1088 return request.user.isReadOnlyUser()
1090 def queryset(self, request):
1091 return User.select_by_user(request.user)
1093 def backend_status_text(self, obj):
1094 return mark_safe(backend_text(obj))
1096 def backend_status_icon(self, obj):
1097 return mark_safe(backend_icon(obj))
1098 backend_status_icon.short_description = ""
1100 class DashboardViewAdmin(PlanetStackBaseAdmin):
1101 fieldsets = [('Dashboard View Details',
1102 {'fields': ['backend_status_text', 'name', 'url'],
1103 'classes': ['suit-tab suit-tab-general']})
1105 readonly_fields = ('backend_status_text', )
1107 suit_form_tabs =(('general','Dashboard View Details'),)
1109 class ServiceResourceInline(PlStackTabularInline):
1110 model = ServiceResource
1113 class ServiceClassAdmin(PlanetStackBaseAdmin):
1114 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1115 list_display_links = ('backend_status_icon', 'name', )
1116 inlines = [ServiceResourceInline]
1118 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1119 user_readonly_inlines = []
1121 class ReservedResourceInline(PlStackTabularInline):
1122 model = ReservedResource
1124 suit_classes = 'suit-tab suit-tab-reservedresources'
1126 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1127 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1129 if db_field.name == 'resource':
1130 # restrict resources to those that the slice's service class allows
1131 if request._slice is not None:
1132 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1133 if len(field.queryset) > 0:
1134 field.initial = field.queryset.all()[0]
1136 field.queryset = field.queryset.none()
\r
1137 elif db_field.name == 'sliver':
\r
1138 # restrict slivers to those that belong to the slice
\r
1139 if request._slice is not None:
\r
1140 field.queryset = field.queryset.filter(slice = request._slice)
1142 field.queryset = field.queryset.none()
\r
1146 def queryset(self, request):
1147 return ReservedResource.select_by_user(request.user)
1149 class ReservationChangeForm(forms.ModelForm):
1153 'slice' : LinkedSelect
1156 class ReservationAddForm(forms.ModelForm):
1157 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1158 refresh = forms.CharField(widget=forms.HiddenInput())
1161 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1163 def clean_slice(self):
1164 slice = self.cleaned_data.get("slice")
1165 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1167 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1173 'slice' : LinkedSelect
1177 class ReservationAddRefreshForm(ReservationAddForm):
1178 """ This form is displayed when the Reservation Form receives an update
1179 from the Slice dropdown onChange handler. It doesn't validate the
1180 data and doesn't save the data. This will cause the form to be
1184 """ don't validate anything other than slice """
1185 dont_validate_fields = ("startTime", "duration")
1187 def full_clean(self):
1188 result = super(ReservationAddForm, self).full_clean()
1190 for fieldname in self.dont_validate_fields:
1191 if fieldname in self._errors:
1192 del self._errors[fieldname]
1196 """ don't save anything """
1200 class ReservationAdmin(PlanetStackBaseAdmin):
1201 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
1202 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1203 readonly_fields = ('backend_status_text', )
1204 list_display = ('startTime', 'duration')
1205 form = ReservationAddForm
1207 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1209 inlines = [ReservedResourceInline]
1210 user_readonly_fields = fieldList
1212 def add_view(self, request, form_url='', extra_context=None):
1213 timezone.activate(request.user.timezone)
1214 request._refresh = False
1215 request._slice = None
1216 if request.method == 'POST':
1217 # "refresh" will be set to "1" if the form was submitted due to
1218 # a change in the Slice dropdown.
1219 if request.POST.get("refresh","1") == "1":
1220 request._refresh = True
1221 request.POST["refresh"] = "0"
1223 # Keep track of the slice that was selected, so the
1224 # reservedResource inline can filter items for the slice.
1225 request._slice = request.POST.get("slice",None)
1226 if (request._slice is not None):
1227 request._slice = Slice.objects.get(id=request._slice)
1229 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1232 def changelist_view(self, request, extra_context = None):
1233 timezone.activate(request.user.timezone)
1234 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1236 def get_form(self, request, obj=None, **kwargs):
1239 # For changes, set request._slice to the slice already set in the
1241 request._slice = obj.slice
1242 self.form = ReservationChangeForm
1244 if getattr(request, "_refresh", False):
1245 self.form = ReservationAddRefreshForm
1247 self.form = ReservationAddForm
1248 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1250 def get_readonly_fields(self, request, obj=None):
1251 if (obj is not None):
1252 # Prevent slice from being changed after the reservation has been
1258 def queryset(self, request):
1259 return Reservation.select_by_user(request.user)
1261 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1262 list_display = ("backend_status_icon", "name", )
1263 list_display_links = ('backend_status_icon', 'name', )
1264 user_readonly_fields = ['name']
1265 user_readonly_inlines = []
1267 class RouterAdmin(PlanetStackBaseAdmin):
1268 list_display = ("backend_status_icon", "name", )
1269 list_display_links = ('backend_status_icon', 'name', )
1270 user_readonly_fields = ['name']
1271 user_readonly_inlines = []
1273 class RouterInline(PlStackTabularInline):
1274 model = Router.networks.through
1276 verbose_name_plural = "Routers"
1277 verbose_name = "Router"
1278 suit_classes = 'suit-tab suit-tab-routers'
1280 class NetworkParameterInline(PlStackGenericTabularInline):
1281 model = NetworkParameter
1283 verbose_name_plural = "Parameters"
1284 verbose_name = "Parameter"
1285 suit_classes = 'suit-tab suit-tab-netparams'
1286 fields = ['backend_status_icon', 'parameter', 'value']
1287 readonly_fields = ('backend_status_icon', )
1289 class NetworkSliversInline(PlStackTabularInline):
1290 fields = ['backend_status_icon', 'network','sliver','ip']
1291 readonly_fields = ("backend_status_icon", "ip", )
1292 model = NetworkSliver
1293 selflink_fieldname = "sliver"
1295 verbose_name_plural = "Slivers"
1296 verbose_name = "Sliver"
1297 suit_classes = 'suit-tab suit-tab-networkslivers'
1299 class NetworkSlicesInline(PlStackTabularInline):
1300 model = NetworkSlice
1301 selflink_fieldname = "slice"
1303 verbose_name_plural = "Slices"
1304 verbose_name = "Slice"
1305 suit_classes = 'suit-tab suit-tab-networkslices'
1306 fields = ['backend_status_icon', 'network','slice']
1307 readonly_fields = ('backend_status_icon', )
1309 class NetworkAdmin(PlanetStackBaseAdmin):
1310 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1311 list_display_links = ('backend_status_icon', 'name', )
1312 readonly_fields = ("subnet", )
1314 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1317 (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']}),]
1319 readonly_fields = ('backend_status_text', )
1320 user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1323 ('general','Network Details'),
1324 ('netparams', 'Parameters'),
1325 ('networkslivers','Slivers'),
1326 ('networkslices','Slices'),
1327 ('routers','Routers'),
1329 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1330 list_display = ("backend_status_icon", "name", "guaranteedBandwidth", "visibility")
1331 list_display_links = ('backend_status_icon', 'name', )
1332 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1333 user_readonly_inlines = []
1335 # register a signal that caches the user's credentials when they log in
1336 def cache_credentials(sender, user, request, **kwds):
1337 auth = {'username': request.POST['username'],
1338 'password': request.POST['password']}
1339 request.session['auth'] = auth
1340 user_logged_in.connect(cache_credentials)
1342 def dollar_field(fieldName, short_description):
1343 def newFunc(self, obj):
1345 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1347 x=getattr(obj, fieldName, 0.0)
1349 newFunc.short_description = short_description
1352 def right_dollar_field(fieldName, short_description):
1353 def newFunc(self, obj):
1355 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1356 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1358 x=getattr(obj, fieldName, 0.0)
1360 newFunc.short_description = short_description
1361 newFunc.allow_tags = True
1364 class InvoiceChargeInline(PlStackTabularInline):
1367 verbose_name_plural = "Charges"
1368 verbose_name = "Charge"
1369 exclude = ['account']
1370 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1371 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1375 dollar_amount = right_dollar_field("amount", "Amount")
1377 class InvoiceAdmin(admin.ModelAdmin):
1378 list_display = ("date", "account")
1380 inlines = [InvoiceChargeInline]
1382 fields = ["date", "account", "dollar_amount"]
1383 readonly_fields = ["date", "account", "dollar_amount"]
1385 dollar_amount = dollar_field("amount", "Amount")
1387 class InvoiceInline(PlStackTabularInline):
1390 verbose_name_plural = "Invoices"
1391 verbose_name = "Invoice"
1392 fields = ["date", "dollar_amount"]
1393 readonly_fields = ["date", "dollar_amount"]
1394 suit_classes = 'suit-tab suit-tab-accountinvoice'
1398 dollar_amount = right_dollar_field("amount", "Amount")
1400 class PendingChargeInline(PlStackTabularInline):
1403 verbose_name_plural = "Charges"
1404 verbose_name = "Charge"
1405 exclude = ["invoice"]
1406 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1407 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1408 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1412 def queryset(self, request):
1413 qs = super(PendingChargeInline, self).queryset(request)
1414 qs = qs.filter(state="pending")
1417 dollar_amount = right_dollar_field("amount", "Amount")
1419 class PaymentInline(PlStackTabularInline):
1422 verbose_name_plural = "Payments"
1423 verbose_name = "Payment"
1424 fields = ["date", "dollar_amount"]
1425 readonly_fields = ["date", "dollar_amount"]
1426 suit_classes = 'suit-tab suit-tab-accountpayments'
1430 dollar_amount = right_dollar_field("amount", "Amount")
1432 class AccountAdmin(admin.ModelAdmin):
1433 list_display = ("site", "balance_due")
1435 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1438 (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1440 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1443 ('general','Account Details'),
1444 ('accountinvoice', 'Invoices'),
1445 ('accountpayments', 'Payments'),
1446 ('accountpendingcharges','Pending Charges'),
1449 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1450 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1451 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1454 # Now register the new UserAdmin...
1455 admin.site.register(User, UserAdmin)
1456 # ... and, since we're not using Django's builtin permissions,
1457 # unregister the Group model from admin.
1458 #admin.site.unregister(Group)
1460 #Do not show django evolution in the admin interface
1461 from django_evolution.models import Version, Evolution
1462 #admin.site.unregister(Version)
1463 #admin.site.unregister(Evolution)
1466 # When debugging it is often easier to see all the classes, but for regular use
1467 # only the top-levels should be displayed
1470 admin.site.register(Deployment, DeploymentAdmin)
1471 admin.site.register(Site, SiteAdmin)
1472 admin.site.register(Slice, SliceAdmin)
1473 admin.site.register(Service, ServiceAdmin)
1474 admin.site.register(Reservation, ReservationAdmin)
1475 admin.site.register(Network, NetworkAdmin)
1476 admin.site.register(Router, RouterAdmin)
1477 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1478 admin.site.register(Account, AccountAdmin)
1479 admin.site.register(Invoice, InvoiceAdmin)
1482 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1483 admin.site.register(ServiceClass, ServiceClassAdmin)
1484 #admin.site.register(PlanetStack)
1485 admin.site.register(Tag, TagAdmin)
1486 admin.site.register(DeploymentRole)
1487 admin.site.register(SiteRole)
1488 admin.site.register(SliceRole)
1489 admin.site.register(PlanetStackRole)
1490 admin.site.register(Node, NodeAdmin)
1491 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1492 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1493 admin.site.register(Sliver, SliverAdmin)
1494 admin.site.register(Image, ImageAdmin)
1495 admin.site.register(DashboardView, DashboardViewAdmin)