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 '<span title="%s"><img src="/static/admin/img/icon_clock.gif"></span>' % obj.backend_status
29 return '<span 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', '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', 'site', 'name', '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) )
759 for site in Site.objects.all():
760 sites[site.id] = site.login_base
762 context["deployment_nodes"] = deployment_nodes
763 context["sites"] = sites
765 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
767 def formfield_for_foreignkey(self, db_field, request, **kwargs):
768 if db_field.name == 'site':
769 kwargs['queryset'] = Site.select_by_user(request.user)
770 kwargs['widget'] = forms.Select(attrs={'onChange': "update_slice_name(this, $($(this).closest('div')[0]).find('.field-name input')[0].id)"})
772 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
774 def queryset(self, request):
775 # admins can see all keys. Users can only see slices they belong to.
776 return Slice.select_by_user(request.user)
778 def get_formsets(self, request, obj=None):
779 for inline in self.get_inline_instances(request, obj):
780 # hide MyInline in the add view
783 if isinstance(inline, SliverInline):
784 inline.model.caller = request.user
785 yield inline.get_formset(request, obj)
788 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
790 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
792 readonly_fields = ('backend_status_text', )
793 list_display = ('backend_status_icon', 'user', 'slice', 'role')
794 list_display_links = list_display
796 user_readonly_fields = ['user', 'slice', 'role']
797 user_readonly_inlines = []
799 def formfield_for_foreignkey(self, db_field, request, **kwargs):
800 if db_field.name == 'slice':
801 kwargs['queryset'] = Slice.select_by_user(request.user)
803 if db_field.name == 'user':
804 kwargs['queryset'] = User.select_by_user(request.user)
806 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
808 def queryset(self, request):
809 # admins can see all memberships. Users can only see memberships of
810 # slices where they have the admin role.
811 return SlicePrivilege.select_by_user(request.user)
813 def save_model(self, request, obj, form, change):
814 # update openstack connection to use this site/tenant
815 auth = request.session.get('auth', {})
816 auth['tenant'] = obj.slice.slicename
817 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
820 def delete_model(self, request, obj):
821 # update openstack connection to use this site/tenant
822 auth = request.session.get('auth', {})
823 auth['tenant'] = obj.slice.slicename
824 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
828 class ImageAdmin(PlanetStackBaseAdmin):
830 fieldsets = [('Image Details',
831 {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
832 'classes': ['suit-tab suit-tab-general']})
834 readonly_fields = ('backend_status_text', )
836 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
838 inlines = [SliverInline, ImageDeploymentsInline]
840 user_readonly_fields = ['name', 'disk_format', 'container_format']
842 list_display = ['backend_status_icon', 'name']
843 list_display_links = ('backend_status_icon', 'name', )
845 class NodeForm(forms.ModelForm):
848 'site': LinkedSelect,
849 'deployment': LinkedSelect
852 class NodeAdmin(PlanetStackBaseAdmin):
854 list_display = ('backend_status_icon', 'name', 'site', 'deployment')
855 list_display_links = ('backend_status_icon', 'name', )
856 list_filter = ('deployment',)
858 inlines = [TagInline,SliverInline]
859 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
860 readonly_fields = ('backend_status_text', )
862 user_readonly_fields = ['name','site','deployment']
863 user_readonly_inlines = [TagInline,SliverInline]
865 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
868 class SliverForm(forms.ModelForm):
871 ip = forms.CharField(widget=PlainTextWidget)
872 instance_name = forms.CharField(widget=PlainTextWidget)
874 'ip': PlainTextWidget(),
875 'instance_name': PlainTextWidget(),
876 'slice': LinkedSelect,
877 'deploymentNetwork': LinkedSelect,
878 'node': LinkedSelect,
879 'image': LinkedSelect
882 class TagAdmin(PlanetStackBaseAdmin):
883 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
884 list_display_links = list_display
885 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
886 user_readonly_inlines = []
888 class SliverAdmin(PlanetStackBaseAdmin):
891 ('Sliver Details', {'fields': ['backend_status_text', 'slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
893 readonly_fields = ('backend_status_text', )
894 list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']
895 list_display_links = ('backend_status_icon', 'ip',)
897 suit_form_tabs =(('general', 'Sliver Details'),
901 inlines = [TagInline]
903 user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image']
905 def formfield_for_foreignkey(self, db_field, request, **kwargs):
906 if db_field.name == 'slice':
907 kwargs['queryset'] = Slice.select_by_user(request.user)
909 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
911 def queryset(self, request):
912 # admins can see all slivers. Users can only see slivers of
913 # the slices they belong to.
914 return Sliver.select_by_user(request.user)
917 def get_formsets(self, request, obj=None):
918 # make some fields read only if we are updating an existing record
920 #self.readonly_fields = ('ip', 'instance_name')
921 self.readonly_fields = ('backend_status_text')
923 self.readonly_fields = ('backend_status_text')
924 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
926 for inline in self.get_inline_instances(request, obj):
927 # hide MyInline in the add view
930 if isinstance(inline, SliverInline):
931 inline.model.caller = request.user
932 yield inline.get_formset(request, obj)
934 #def save_model(self, request, obj, form, change):
935 # # update openstack connection to use this site/tenant
936 # auth = request.session.get('auth', {})
937 # auth['tenant'] = obj.slice.name
938 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
939 # obj.creator = request.user
942 #def delete_model(self, request, obj):
943 # # update openstack connection to use this site/tenant
944 # auth = request.session.get('auth', {})
945 # auth['tenant'] = obj.slice.name
946 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
949 class UserCreationForm(forms.ModelForm):
950 """A form for creating new users. Includes all the required
951 fields, plus a repeated password."""
952 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
953 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
957 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
959 def clean_password2(self):
960 # Check that the two password entries match
961 password1 = self.cleaned_data.get("password1")
962 password2 = self.cleaned_data.get("password2")
963 if password1 and password2 and password1 != password2:
964 raise forms.ValidationError("Passwords don't match")
967 def save(self, commit=True):
968 # Save the provided password in hashed format
969 user = super(UserCreationForm, self).save(commit=False)
970 user.password = self.cleaned_data["password1"]
971 #user.set_password(self.cleaned_data["password1"])
977 class UserChangeForm(forms.ModelForm):
978 """A form for updating users. Includes all the fields on
979 the user, but replaces the password field with admin's
980 password hash display field.
982 password = ReadOnlyPasswordHashField(label='Password',
983 help_text= '<a href=\"password/\">Change Password</a>.')
988 def clean_password(self):
989 # Regardless of what the user provides, return the initial value.
990 # This is done here, rather than on the field, because the
991 # field does not have access to the initial value
992 return self.initial["password"]
994 class UserDashboardViewInline(PlStackTabularInline):
995 model = UserDashboardView
997 suit_classes = 'suit-tab suit-tab-dashboards'
998 fields = ['user', 'dashboardView', 'order']
1000 class UserAdmin(UserAdmin):
1004 # The forms to add and change user instances
1005 form = UserChangeForm
1006 add_form = UserCreationForm
1008 # The fields to be used in displaying the User model.
1009 # These override the definitions on the base UserAdmin
1010 # that reference specific fields on auth.User.
1011 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
1012 list_filter = ('site',)
1013 inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
1015 fieldListLoginDetails = ['email','site','password','is_active','is_readonly','is_admin','public_key']
1016 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1019 ('Login Details', {'fields': ['backend_status_text', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1020 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1021 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1022 #('Important dates', {'fields': ('last_login',)}),
1026 'classes': ('wide',),
1027 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
1030 readonly_fields = ('backend_status_text', )
1031 search_fields = ('email',)
1032 ordering = ('email',)
1033 filter_horizontal = ()
1035 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1037 suit_form_tabs =(('general','Login Details'),
1038 ('contact','Contact Information'),
1039 ('sliceprivileges','Slice Privileges'),
1040 ('siteprivileges','Site Privileges'),
1041 ('deploymentprivileges','Deployment Privileges'),
1042 ('dashboards','Dashboard Views'))
1044 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1045 if db_field.name == 'site':
1046 kwargs['queryset'] = Site.select_by_user(request.user)
1048 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1050 def has_add_permission(self, request, obj=None):
1051 return (not self.__user_is_readonly(request))
1053 def has_delete_permission(self, request, obj=None):
1054 return (not self.__user_is_readonly(request))
1056 def get_actions(self,request):
1057 actions = super(UserAdmin,self).get_actions(request)
1059 if self.__user_is_readonly(request):
1060 if 'delete_selected' in actions:
1061 del actions['delete_selected']
1065 def change_view(self,request,object_id, extra_context=None):
1067 if self.__user_is_readonly(request):
1068 if not hasattr(self, "readonly_save"):
1069 # save the original readonly fields
\r
1070 self.readonly_save = self.readonly_fields
\r
1071 self.inlines_save = self.inlines
1072 if hasattr(self, "user_readonly_fields"):
1073 self.readonly_fields=self.user_readonly_fields
1074 if hasattr(self, "user_readonly_inlines"):
1075 self.inlines = self.user_readonly_inlines
1077 if hasattr(self, "readonly_save"):
\r
1078 # restore the original readonly fields
\r
1079 self.readonly_fields = self.readonly_save
\r
1080 self.inlines = self.inlines_save
1083 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1084 except PermissionDenied:
1086 if request.method == 'POST':
1087 raise PermissionDenied
1088 request.readonly = True
1089 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1091 def __user_is_readonly(self, request):
1092 #groups = [x.name for x in request.user.groups.all() ]
1093 #return "readonly" in groups
1094 return request.user.isReadOnlyUser()
1096 def queryset(self, request):
1097 return User.select_by_user(request.user)
1099 def backend_status_text(self, obj):
1100 return mark_safe(backend_text(obj))
1102 def backend_status_icon(self, obj):
1103 return mark_safe(backend_icon(obj))
1104 backend_status_icon.short_description = ""
1106 class DashboardViewAdmin(PlanetStackBaseAdmin):
1107 fieldsets = [('Dashboard View Details',
1108 {'fields': ['backend_status_text', 'name', 'url'],
1109 'classes': ['suit-tab suit-tab-general']})
1111 readonly_fields = ('backend_status_text', )
1113 suit_form_tabs =(('general','Dashboard View Details'),)
1115 class ServiceResourceInline(PlStackTabularInline):
1116 model = ServiceResource
1119 class ServiceClassAdmin(PlanetStackBaseAdmin):
1120 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1121 list_display_links = ('backend_status_icon', 'name', )
1122 inlines = [ServiceResourceInline]
1124 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1125 user_readonly_inlines = []
1127 class ReservedResourceInline(PlStackTabularInline):
1128 model = ReservedResource
1130 suit_classes = 'suit-tab suit-tab-reservedresources'
1132 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1133 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1135 if db_field.name == 'resource':
1136 # restrict resources to those that the slice's service class allows
1137 if request._slice is not None:
1138 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1139 if len(field.queryset) > 0:
1140 field.initial = field.queryset.all()[0]
1142 field.queryset = field.queryset.none()
\r
1143 elif db_field.name == 'sliver':
\r
1144 # restrict slivers to those that belong to the slice
\r
1145 if request._slice is not None:
\r
1146 field.queryset = field.queryset.filter(slice = request._slice)
1148 field.queryset = field.queryset.none()
\r
1152 def queryset(self, request):
1153 return ReservedResource.select_by_user(request.user)
1155 class ReservationChangeForm(forms.ModelForm):
1159 'slice' : LinkedSelect
1162 class ReservationAddForm(forms.ModelForm):
1163 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1164 refresh = forms.CharField(widget=forms.HiddenInput())
1167 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1169 def clean_slice(self):
1170 slice = self.cleaned_data.get("slice")
1171 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1173 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1179 'slice' : LinkedSelect
1183 class ReservationAddRefreshForm(ReservationAddForm):
1184 """ This form is displayed when the Reservation Form receives an update
1185 from the Slice dropdown onChange handler. It doesn't validate the
1186 data and doesn't save the data. This will cause the form to be
1190 """ don't validate anything other than slice """
1191 dont_validate_fields = ("startTime", "duration")
1193 def full_clean(self):
1194 result = super(ReservationAddForm, self).full_clean()
1196 for fieldname in self.dont_validate_fields:
1197 if fieldname in self._errors:
1198 del self._errors[fieldname]
1202 """ don't save anything """
1206 class ReservationAdmin(PlanetStackBaseAdmin):
1207 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
1208 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1209 readonly_fields = ('backend_status_text', )
1210 list_display = ('startTime', 'duration')
1211 form = ReservationAddForm
1213 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1215 inlines = [ReservedResourceInline]
1216 user_readonly_fields = fieldList
1218 def add_view(self, request, form_url='', extra_context=None):
1219 timezone.activate(request.user.timezone)
1220 request._refresh = False
1221 request._slice = None
1222 if request.method == 'POST':
1223 # "refresh" will be set to "1" if the form was submitted due to
1224 # a change in the Slice dropdown.
1225 if request.POST.get("refresh","1") == "1":
1226 request._refresh = True
1227 request.POST["refresh"] = "0"
1229 # Keep track of the slice that was selected, so the
1230 # reservedResource inline can filter items for the slice.
1231 request._slice = request.POST.get("slice",None)
1232 if (request._slice is not None):
1233 request._slice = Slice.objects.get(id=request._slice)
1235 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1238 def changelist_view(self, request, extra_context = None):
1239 timezone.activate(request.user.timezone)
1240 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1242 def get_form(self, request, obj=None, **kwargs):
1245 # For changes, set request._slice to the slice already set in the
1247 request._slice = obj.slice
1248 self.form = ReservationChangeForm
1250 if getattr(request, "_refresh", False):
1251 self.form = ReservationAddRefreshForm
1253 self.form = ReservationAddForm
1254 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1256 def get_readonly_fields(self, request, obj=None):
1257 if (obj is not None):
1258 # Prevent slice from being changed after the reservation has been
1264 def queryset(self, request):
1265 return Reservation.select_by_user(request.user)
1267 class NetworkParameterTypeAdmin(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 RouterAdmin(PlanetStackBaseAdmin):
1274 list_display = ("backend_status_icon", "name", )
1275 list_display_links = ('backend_status_icon', 'name', )
1276 user_readonly_fields = ['name']
1277 user_readonly_inlines = []
1279 class RouterInline(PlStackTabularInline):
1280 model = Router.networks.through
1282 verbose_name_plural = "Routers"
1283 verbose_name = "Router"
1284 suit_classes = 'suit-tab suit-tab-routers'
1286 class NetworkParameterInline(PlStackGenericTabularInline):
1287 model = NetworkParameter
1289 verbose_name_plural = "Parameters"
1290 verbose_name = "Parameter"
1291 suit_classes = 'suit-tab suit-tab-netparams'
1292 fields = ['backend_status_icon', 'parameter', 'value']
1293 readonly_fields = ('backend_status_icon', )
1295 class NetworkSliversInline(PlStackTabularInline):
1296 fields = ['backend_status_icon', 'network','sliver','ip']
1297 readonly_fields = ("backend_status_icon", "ip", )
1298 model = NetworkSliver
1299 selflink_fieldname = "sliver"
1301 verbose_name_plural = "Slivers"
1302 verbose_name = "Sliver"
1303 suit_classes = 'suit-tab suit-tab-networkslivers'
1305 class NetworkSlicesInline(PlStackTabularInline):
1306 model = NetworkSlice
1307 selflink_fieldname = "slice"
1309 verbose_name_plural = "Slices"
1310 verbose_name = "Slice"
1311 suit_classes = 'suit-tab suit-tab-networkslices'
1312 fields = ['backend_status_icon', 'network','slice']
1313 readonly_fields = ('backend_status_icon', )
1315 class NetworkAdmin(PlanetStackBaseAdmin):
1316 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1317 list_display_links = ('backend_status_icon', 'name', )
1318 readonly_fields = ("subnet", )
1320 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1323 (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']}),]
1325 readonly_fields = ('backend_status_text', )
1326 user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1329 ('general','Network Details'),
1330 ('netparams', 'Parameters'),
1331 ('networkslivers','Slivers'),
1332 ('networkslices','Slices'),
1333 ('routers','Routers'),
1335 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1336 list_display = ("backend_status_icon", "name", "guaranteedBandwidth", "visibility")
1337 list_display_links = ('backend_status_icon', 'name', )
1338 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1339 user_readonly_inlines = []
1341 # register a signal that caches the user's credentials when they log in
1342 def cache_credentials(sender, user, request, **kwds):
1343 auth = {'username': request.POST['username'],
1344 'password': request.POST['password']}
1345 request.session['auth'] = auth
1346 user_logged_in.connect(cache_credentials)
1348 def dollar_field(fieldName, short_description):
1349 def newFunc(self, obj):
1351 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1353 x=getattr(obj, fieldName, 0.0)
1355 newFunc.short_description = short_description
1358 def right_dollar_field(fieldName, short_description):
1359 def newFunc(self, obj):
1361 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1362 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1364 x=getattr(obj, fieldName, 0.0)
1366 newFunc.short_description = short_description
1367 newFunc.allow_tags = True
1370 class InvoiceChargeInline(PlStackTabularInline):
1373 verbose_name_plural = "Charges"
1374 verbose_name = "Charge"
1375 exclude = ['account']
1376 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1377 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1381 dollar_amount = right_dollar_field("amount", "Amount")
1383 class InvoiceAdmin(admin.ModelAdmin):
1384 list_display = ("date", "account")
1386 inlines = [InvoiceChargeInline]
1388 fields = ["date", "account", "dollar_amount"]
1389 readonly_fields = ["date", "account", "dollar_amount"]
1391 dollar_amount = dollar_field("amount", "Amount")
1393 class InvoiceInline(PlStackTabularInline):
1396 verbose_name_plural = "Invoices"
1397 verbose_name = "Invoice"
1398 fields = ["date", "dollar_amount"]
1399 readonly_fields = ["date", "dollar_amount"]
1400 suit_classes = 'suit-tab suit-tab-accountinvoice'
1404 dollar_amount = right_dollar_field("amount", "Amount")
1406 class PendingChargeInline(PlStackTabularInline):
1409 verbose_name_plural = "Charges"
1410 verbose_name = "Charge"
1411 exclude = ["invoice"]
1412 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1413 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1414 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1418 def queryset(self, request):
1419 qs = super(PendingChargeInline, self).queryset(request)
1420 qs = qs.filter(state="pending")
1423 dollar_amount = right_dollar_field("amount", "Amount")
1425 class PaymentInline(PlStackTabularInline):
1428 verbose_name_plural = "Payments"
1429 verbose_name = "Payment"
1430 fields = ["date", "dollar_amount"]
1431 readonly_fields = ["date", "dollar_amount"]
1432 suit_classes = 'suit-tab suit-tab-accountpayments'
1436 dollar_amount = right_dollar_field("amount", "Amount")
1438 class AccountAdmin(admin.ModelAdmin):
1439 list_display = ("site", "balance_due")
1441 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1444 (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1446 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1449 ('general','Account Details'),
1450 ('accountinvoice', 'Invoices'),
1451 ('accountpayments', 'Payments'),
1452 ('accountpendingcharges','Pending Charges'),
1455 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1456 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1457 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1460 # Now register the new UserAdmin...
1461 admin.site.register(User, UserAdmin)
1462 # ... and, since we're not using Django's builtin permissions,
1463 # unregister the Group model from admin.
1464 #admin.site.unregister(Group)
1466 #Do not show django evolution in the admin interface
1467 from django_evolution.models import Version, Evolution
1468 #admin.site.unregister(Version)
1469 #admin.site.unregister(Evolution)
1472 # When debugging it is often easier to see all the classes, but for regular use
1473 # only the top-levels should be displayed
1476 admin.site.register(Deployment, DeploymentAdmin)
1477 admin.site.register(Site, SiteAdmin)
1478 admin.site.register(Slice, SliceAdmin)
1479 admin.site.register(Service, ServiceAdmin)
1480 admin.site.register(Reservation, ReservationAdmin)
1481 admin.site.register(Network, NetworkAdmin)
1482 admin.site.register(Router, RouterAdmin)
1483 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1484 admin.site.register(Account, AccountAdmin)
1485 admin.site.register(Invoice, InvoiceAdmin)
1488 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1489 admin.site.register(ServiceClass, ServiceClassAdmin)
1490 #admin.site.register(PlanetStack)
1491 admin.site.register(Tag, TagAdmin)
1492 admin.site.register(DeploymentRole)
1493 admin.site.register(SiteRole)
1494 admin.site.register(SliceRole)
1495 admin.site.register(PlanetStackRole)
1496 admin.site.register(Node, NodeAdmin)
1497 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1498 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1499 admin.site.register(Sliver, SliverAdmin)
1500 admin.site.register(Image, ImageAdmin)
1501 admin.site.register(DashboardView, DashboardViewAdmin)