1 from core.models import Site
2 from core.models import *
3 from openstack.manager import OpenStackManager
5 from django.contrib import admin
6 from django.contrib.auth.models import Group
7 from django import forms
8 from django.utils.safestring import mark_safe
9 from django.contrib.auth.admin import UserAdmin
10 from django.contrib.admin.widgets import FilteredSelectMultiple
11 from django.contrib.auth.forms import ReadOnlyPasswordHashField, AdminPasswordChangeForm
12 from django.contrib.auth.signals import user_logged_in
13 from django.utils import timezone
14 from django.contrib.contenttypes import generic
15 from suit.widgets import LinkedSelect
16 from django.core.exceptions import PermissionDenied
17 from django.core.urlresolvers import reverse, NoReverseMatch
19 import django_evolution
21 def backend_icon(obj): # backend_status, enacted, updated):
22 #return "%s %s %s" % (str(obj.updated), str(obj.enacted), str(obj.backend_status))
23 if (obj.enacted is not None) and obj.enacted >= obj.updated:
24 return '<span style="min-width:16px;"><img src="/static/admin/img/icon_success.gif"></span>'
26 if obj.backend_status == "Provisioning in progress" or obj.backend_status=="":
27 return '<span style="min-width:16px;" title="%s"><img src="/static/admin/img/icon_clock.gif"></span>' % obj.backend_status
29 return '<span style="min-width:16px;" title="%s"><img src="/static/admin/img/icon_error.gif"></span>' % obj.backend_status
31 def backend_text(obj):
32 icon = backend_icon(obj)
33 if (obj.enacted is not None) and obj.enacted >= obj.updated:
34 return "%s %s" % (icon, "successfully enacted") # enacted on %s" % str(obj.enacted))
36 return "%s %s" % (icon, obj.backend_status)
38 class PlainTextWidget(forms.HiddenInput):
41 def render(self, name, value, attrs=None):
44 return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
46 class PermissionCheckingAdminMixin(object):
47 # call save_by_user and delete_by_user instead of save and delete
49 def has_add_permission(self, request, obj=None):
50 return (not self.__user_is_readonly(request))
52 def has_delete_permission(self, request, obj=None):
53 return (not self.__user_is_readonly(request))
55 def save_model(self, request, obj, form, change):
56 if self.__user_is_readonly(request):
57 # this 'if' might be redundant if save_by_user is implemented right
58 raise PermissionDenied
60 obj.caller = request.user
61 # update openstack connection to use this site/tenant
62 obj.save_by_user(request.user)
64 def delete_model(self, request, obj):
65 obj.delete_by_user(request.user)
67 def save_formset(self, request, form, formset, change):
68 instances = formset.save(commit=False)
69 for instance in instances:
70 instance.save_by_user(request.user)
72 # BUG in django 1.7? Objects are not deleted by formset.save if
73 # commit is False. So let's delete them ourselves.
75 # code from forms/models.py save_existing_objects()
77 forms_to_delete = formset.deleted_forms
\r
78 except AttributeError:
\r
80 if formset.initial_forms:
81 for form in formset.initial_forms:
83 if form in forms_to_delete:
86 formset.deleted_objects.append(obj)
91 def get_actions(self,request):
92 actions = super(PermissionCheckingAdminMixin,self).get_actions(request)
94 if self.__user_is_readonly(request):
95 if 'delete_selected' in actions:
96 del actions['delete_selected']
100 def change_view(self,request,object_id, extra_context=None):
101 if self.__user_is_readonly(request):
102 if not hasattr(self, "readonly_save"):
\r
103 # save the original readonly fields
\r
104 self.readonly_save = self.readonly_fields
\r
105 self.inlines_save = self.inlines
\r
106 if hasattr(self, "user_readonly_fields"):
\r
107 self.readonly_fields=self.user_readonly_fields
\r
108 if hasattr(self, "user_readonly_inlines"):
\r
109 self.inlines = self.user_readonly_inlines
\r
111 if hasattr(self, "readonly_save"):
\r
112 # restore the original readonly fields
\r
113 self.readonly_fields = self.readonly_save
\r
114 if hasattr(self, "inlines_save"):
\r
115 self.inlines = self.inlines_save
118 return super(PermissionCheckingAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
119 except PermissionDenied:
121 if request.method == 'POST':
122 raise PermissionDenied
123 request.readonly = True
124 return super(PermissionCheckingAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
126 def __user_is_readonly(self, request):
127 return request.user.isReadOnlyUser()
129 def backend_status_text(self, obj):
130 return mark_safe(backend_text(obj))
132 def backend_status_icon(self, obj):
133 return mark_safe(backend_icon(obj))
134 backend_status_icon.short_description = ""
136 class ReadOnlyAwareAdmin(PermissionCheckingAdminMixin, admin.ModelAdmin):
137 # Note: Make sure PermissionCheckingAdminMixin is listed before
138 # admin.ModelAdmin in the class declaration.
142 class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
145 class SingletonAdmin (ReadOnlyAwareAdmin):
146 def has_add_permission(self, request):
147 if not super(SingletonAdmin, self).has_add_permission(request):
150 num_objects = self.model.objects.count()
156 class PlStackTabularInline(admin.TabularInline):
157 def __init__(self, *args, **kwargs):
158 super(PlStackTabularInline, self).__init__(*args, **kwargs)
160 # InlineModelAdmin as no get_fields() method, so in order to add
161 # the selflink field, we override __init__ to modify self.fields and
162 # self.readonly_fields.
164 self.setup_selflink()
166 def get_change_url(self, model, id):
167 """ Get the URL to a change form in the admin for this model """
168 reverse_path = "admin:%s_change" % (model._meta.db_table)
170 url = reverse(reverse_path, args=(id,))
171 except NoReverseMatch:
176 def setup_selflink(self):
177 if hasattr(self, "selflink_fieldname"):
178 """ self.selflink_model can be defined to punch through a relation
179 to its target object. For example, in SliceNetworkInline, set
180 selflink_model = "network", and the URL will lead to the Network
181 object instead of trying to bring up a change view of the
184 self.selflink_model = getattr(self.model,self.selflink_fieldname).field.rel.to
186 self.selflink_model = self.model
188 url = self.get_change_url(self.selflink_model, 0)
190 # We don't have an admin for this object, so don't create the
195 # Since we need to add "selflink" to the field list, we need to create
196 # self.fields if it is None.
197 if (self.fields is None):
199 for f in self.model._meta.fields:
200 if f.editable and f.name != "id":
201 self.fields.append(f.name)
203 self.fields = tuple(self.fields) + ("selflink", )
205 if self.readonly_fields is None:
206 self.readonly_fields = ()
208 self.readonly_fields = tuple(self.readonly_fields) + ("selflink", )
210 def selflink(self, obj):
211 if hasattr(self, "selflink_fieldname"):
212 obj = getattr(obj, self.selflink_fieldname)
215 url = self.get_change_url(self.selflink_model, obj.id)
216 return "<a href='%s'>Details</a>" % str(url)
218 return "Not present"
\r
220 selflink.allow_tags = True
221 selflink.short_description = "Details"
223 def has_add_permission(self, request):
224 return not request.user.isReadOnlyUser()
226 def get_readonly_fields(self, request, obj=None):
227 readonly_fields = list(self.readonly_fields)[:]
228 if request.user.isReadOnlyUser():
229 for field in self.fields:
230 if not field in readonly_fields:
231 readonly_fields.append(field)
232 return readonly_fields
234 def backend_status_icon(self, obj):
235 return mark_safe(backend_icon(obj))
236 backend_status_icon.short_description = ""
238 class PlStackGenericTabularInline(generic.GenericTabularInline):
239 def has_add_permission(self, request):
240 return not request.user.isReadOnlyUser()
242 def get_readonly_fields(self, request, obj=None):
243 readonly_fields = list(self.readonly_fields)[:]
244 if request.user.isReadOnlyUser():
245 for field in self.fields:
246 if not field in readonly_fields:
247 readonly_fields.append(field)
248 return readonly_fields
250 def backend_status_icon(self, obj):
251 return mark_safe(backend_icon(obj))
252 backend_status_icon.short_description = ""
254 class ReservationInline(PlStackTabularInline):
257 suit_classes = 'suit-tab suit-tab-reservations'
259 def queryset(self, request):
260 return Reservation.select_by_user(request.user)
262 class TagInline(PlStackGenericTabularInline):
265 suit_classes = 'suit-tab suit-tab-tags'
266 fields = ['service', 'name', 'value']
268 def queryset(self, request):
269 return Tag.select_by_user(request.user)
271 class NetworkLookerUpper:
272 """ This is a callable that looks up a network name in a sliver and returns
273 the ip address for that network.
276 byNetworkName = {} # class variable
278 def __init__(self, name):
279 self.short_description = name
281 self.network_name = name
283 def __call__(self, obj):
285 for nbs in obj.networksliver_set.all():
286 if (nbs.network.name == self.network_name):
291 return self.network_name
294 def get(network_name):
295 """ We want to make sure we alwars return the same NetworkLookerUpper
296 because sometimes django will cause them to be instantiated multiple
297 times (and we don't want different ones in form.fields vs
298 SliverInline.readonly_fields).
300 if network_name not in NetworkLookerUpper.byNetworkName:
301 NetworkLookerUpper.byNetworkName[network_name] = NetworkLookerUpper(network_name)
302 return NetworkLookerUpper.byNetworkName[network_name]
304 class SliverInline(PlStackTabularInline):
306 fields = ['backend_status_icon', 'all_ips_string', 'instance_name', 'slice', 'deploymentNetwork', 'flavor', 'image', 'node']
308 readonly_fields = ['backend_status_icon', 'all_ips_string', 'instance_name']
309 suit_classes = 'suit-tab suit-tab-slivers'
311 def queryset(self, request):
312 return Sliver.select_by_user(request.user)
314 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
315 if db_field.name == 'deploymentNetwork':
316 kwargs['queryset'] = Deployment.select_by_acl(request.user)
317 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_deployment_changed(this);"})
318 elif db_field.name == 'flavor':
319 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_flavor_changed(this);"})
321 field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
325 class SiteInline(PlStackTabularInline):
328 suit_classes = 'suit-tab suit-tab-sites'
330 def queryset(self, request):
331 return Site.select_by_user(request.user)
333 class UserInline(PlStackTabularInline):
335 fields = ['backend_status_icon', 'email', 'firstname', 'lastname']
336 readonly_fields = ('backend_status_icon', )
338 suit_classes = 'suit-tab suit-tab-users'
340 def queryset(self, request):
341 return User.select_by_user(request.user)
343 class SliceInline(PlStackTabularInline):
345 fields = ['backend_status_icon', 'name', 'site', 'serviceClass', 'service']
346 readonly_fields = ('backend_status_icon', )
348 suit_classes = 'suit-tab suit-tab-slices'
350 def queryset(self, request):
351 return Slice.select_by_user(request.user)
353 class NodeInline(PlStackTabularInline):
356 suit_classes = 'suit-tab suit-tab-nodes'
357 fields = ['backend_status_icon', 'name','deployment','site']
358 readonly_fields = ('backend_status_icon', )
360 class DeploymentPrivilegeInline(PlStackTabularInline):
361 model = DeploymentPrivilege
363 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
364 fields = ['backend_status_icon', 'user','role','deployment']
365 readonly_fields = ('backend_status_icon', )
367 def queryset(self, request):
368 return DeploymentPrivilege.select_by_user(request.user)
370 class SitePrivilegeInline(PlStackTabularInline):
371 model = SitePrivilege
373 suit_classes = 'suit-tab suit-tab-siteprivileges'
374 fields = ['backend_status_icon', 'user','site', 'role']
375 readonly_fields = ('backend_status_icon', )
377 def formfield_for_foreignkey(self, db_field, request, **kwargs):
378 if db_field.name == 'site':
379 kwargs['queryset'] = Site.select_by_user(request.user)
381 if db_field.name == 'user':
382 kwargs['queryset'] = User.select_by_user(request.user)
383 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
385 def queryset(self, request):
386 return SitePrivilege.select_by_user(request.user)
388 class SiteDeploymentInline(PlStackTabularInline):
389 model = SiteDeployments
391 suit_classes = 'suit-tab suit-tab-deployments'
392 fields = ['backend_status_icon', 'deployment','site']
393 readonly_fields = ('backend_status_icon', )
395 def formfield_for_foreignkey(self, db_field, request, **kwargs):
396 if db_field.name == 'site':
397 kwargs['queryset'] = Site.select_by_user(request.user)
399 if db_field.name == 'deployment':
400 kwargs['queryset'] = Deployment.select_by_user(request.user)
401 return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
403 def queryset(self, request):
404 return SiteDeployments.select_by_user(request.user)
407 class SlicePrivilegeInline(PlStackTabularInline):
408 model = SlicePrivilege
409 suit_classes = 'suit-tab suit-tab-sliceprivileges'
411 fields = ('backend_status_icon', 'user', 'slice', 'role')
412 readonly_fields = ('backend_status_icon', )
414 def formfield_for_foreignkey(self, db_field, request, **kwargs):
415 if db_field.name == 'slice':
416 kwargs['queryset'] = Slice.select_by_user(request.user)
417 if db_field.name == 'user':
418 kwargs['queryset'] = User.select_by_user(request.user)
420 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
422 def queryset(self, request):
423 return SlicePrivilege.select_by_user(request.user)
425 class SliceNetworkInline(PlStackTabularInline):
426 model = Network.slices.through
427 selflink_fieldname = "network"
429 verbose_name = "Network Connection"
430 verbose_name_plural = "Network Connections"
431 suit_classes = 'suit-tab suit-tab-slicenetworks'
432 fields = ['backend_status_icon', 'network']
433 readonly_fields = ('backend_status_icon', )
435 class ImageDeploymentsInline(PlStackTabularInline):
436 model = ImageDeployments
438 verbose_name = "Image Deployments"
439 verbose_name_plural = "Image Deployments"
440 suit_classes = 'suit-tab suit-tab-imagedeployments'
441 fields = ['backend_status_icon', 'image', 'deployment', 'glance_image_id']
442 readonly_fields = ['backend_status_icon', 'glance_image_id']
444 class SliceRoleAdmin(PlanetStackBaseAdmin):
448 class SiteRoleAdmin(PlanetStackBaseAdmin):
452 class DeploymentAdminForm(forms.ModelForm):
453 sites = forms.ModelMultipleChoiceField(
454 queryset=Site.objects.all(),
456 help_text="Select which sites are allowed to host nodes in this deployment",
457 widget=FilteredSelectMultiple(
458 verbose_name=('Sites'), is_stacked=False
461 images = forms.ModelMultipleChoiceField(
462 queryset=Image.objects.all(),
464 help_text="Select which images should be deployed on this deployment",
465 widget=FilteredSelectMultiple(
466 verbose_name=('Images'), is_stacked=False
469 flavors = forms.ModelMultipleChoiceField(
470 queryset=Flavor.objects.all(),
472 help_text="Select which flavors should be usable on this deployment",
473 widget=FilteredSelectMultiple(
474 verbose_name=('Flavors'), is_stacked=False
479 many_to_many = ["flavors",]
481 def __init__(self, *args, **kwargs):
482 request = kwargs.pop('request', None)
483 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
485 self.fields['accessControl'].initial = "allow site " + request.user.site.name
487 if self.instance and self.instance.pk:
488 self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments_set.all()]
489 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments_set.all()]
490 self.fields['flavors'].initial = self.instance.flavors.all()
492 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
493 """ helper function for handling m2m relations from the MultipleChoiceField
495 this_obj: the source object we want to link from
497 selected_objs: a list of destination objects we want to link to
499 all_relations: the full set of relations involving this_obj, including ones we don't want
501 relation_class: the class that implements the relation from source to dest
503 local_attrname: field name representing this_obj in relation_class
505 foreign_attrname: field name representing selected_objs in relation_class
507 This function will remove all newobjclass relations from this_obj
508 that are not contained in selected_objs, and add any relations that
509 are in selected_objs but don't exist in the data model yet.
512 existing_dest_objs = []
513 for relation in list(all_relations):
514 if getattr(relation, foreign_attrname) not in selected_objs:
515 #print "deleting site", sdp.site
518 existing_dest_objs.append(getattr(relation, foreign_attrname))
520 for dest_obj in selected_objs:
521 if dest_obj not in existing_dest_objs:
522 #print "adding site", site
523 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
524 relation = relation_class(**kwargs)
527 def save(self, commit=True):
528 deployment = super(DeploymentAdminForm, self).save(commit=False)
530 deployment.flavors = self.cleaned_data['flavors']
536 # save_m2m() doesn't seem to work with 'through' relations. So we
537 # create/destroy the through models ourselves. There has to be
540 self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments_set.all(), SiteDeployments, "deployment", "site")
541 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments_set.all(), ImageDeployments, "deployment", "image")
547 class DeploymentAdminROForm(DeploymentAdminForm):
548 def save(self, commit=True):
549 raise PermissionDenied
551 class SiteAssocInline(PlStackTabularInline):
552 model = Site.deployments.through
554 suit_classes = 'suit-tab suit-tab-sites'
556 class DeploymentAdmin(PlanetStackBaseAdmin):
558 fieldList = ['backend_status_text', 'name', 'availability_zone', 'sites', 'images', 'flavors', 'accessControl']
559 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
560 inlines = [DeploymentPrivilegeInline,NodeInline,TagInline] # ,ImageDeploymentsInline]
561 list_display = ['backend_status_icon', 'name']
562 list_display_links = ('backend_status_icon', 'name', )
563 readonly_fields = ('backend_status_text', )
565 user_readonly_fields = ['name']
567 suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags')) # ,('imagedeployments','Images'))
569 def get_form(self, request, obj=None, **kwargs):
570 if request.user.isReadOnlyUser():
571 kwargs["form"] = DeploymentAdminROForm
573 kwargs["form"] = DeploymentAdminForm
574 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
576 # from stackexchange: pass the request object into the form
578 class AdminFormMetaClass(adminForm):
579 def __new__(cls, *args, **kwargs):
580 kwargs['request'] = request
581 return adminForm(*args, **kwargs)
583 return AdminFormMetaClass
585 class ServiceAttrAsTabInline(PlStackTabularInline):
586 model = ServiceAttribute
587 fields = ['name','value']
589 suit_classes = 'suit-tab suit-tab-serviceattrs'
591 class ServiceAdmin(PlanetStackBaseAdmin):
592 list_display = ("backend_status_icon","name","description","versionNumber","enabled","published")
593 list_display_links = ('backend_status_icon', 'name', )
594 fieldList = ["backend_status_text","name","description","versionNumber","enabled","published"]
595 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
596 inlines = [ServiceAttrAsTabInline,SliceInline]
597 readonly_fields = ('backend_status_text', )
599 user_readonly_fields = fieldList
601 suit_form_tabs =(('general', 'Service Details'),
603 ('serviceattrs','Additional Attributes'),
606 class SiteAdmin(PlanetStackBaseAdmin):
607 fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
609 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
610 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
612 suit_form_tabs =(('general', 'Site Details'),
614 ('siteprivileges','Privileges'),
615 ('deployments','Deployments'),
620 readonly_fields = ['backend_status_text', 'accountLink']
622 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
624 list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
625 list_display_links = ('backend_status_icon', 'name', )
626 filter_horizontal = ('deployments',)
627 inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentInline]
628 search_fields = ['name']
630 def queryset(self, request):
631 return Site.select_by_user(request.user)
633 def get_formsets(self, request, obj=None):
634 for inline in self.get_inline_instances(request, obj):
635 # hide MyInline in the add view
638 if isinstance(inline, SliceInline):
639 inline.model.caller = request.user
640 yield inline.get_formset(request, obj)
642 def get_formsets(self, request, obj=None):
643 for inline in self.get_inline_instances(request, obj):
644 # hide MyInline in the add view
647 if isinstance(inline, SliverInline):
648 inline.model.caller = request.user
649 yield inline.get_formset(request, obj)
651 def accountLink(self, obj):
652 link_obj = obj.accounts.all()
654 reverse_path = "admin:core_account_change"
655 url = reverse(reverse_path, args =(link_obj[0].id,))
656 return "<a href='%s'>%s</a>" % (url, "view billing details")
658 return "no billing data for this site"
659 accountLink.allow_tags = True
660 accountLink.short_description = "Billing"
662 def save_model(self, request, obj, form, change):
663 # update openstack connection to use this site/tenant
664 obj.save_by_user(request.user)
666 def delete_model(self, request, obj):
667 obj.delete_by_user(request.user)
670 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
671 fieldList = ['backend_status_text', 'user', 'site', 'role']
673 (None, {'fields': fieldList, 'classes':['collapse']})
675 readonly_fields = ('backend_status_text', )
676 list_display = ('backend_status_icon', 'user', 'site', 'role')
677 list_display_links = list_display
678 user_readonly_fields = fieldList
679 user_readonly_inlines = []
681 def formfield_for_foreignkey(self, db_field, request, **kwargs):
682 if db_field.name == 'site':
683 if not request.user.is_admin:
684 # only show sites where user is an admin or pi
686 for site_privilege in SitePrivilege.objects.filer(user=request.user):
687 if site_privilege.role.role_type in ['admin', 'pi']:
688 sites.add(site_privilege.site)
689 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
691 if db_field.name == 'user':
692 if not request.user.is_admin:
693 # only show users from sites where caller has admin or pi role
694 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
695 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
696 sites = [site_privilege.site for site_privilege in site_privileges]
697 site_privileges = SitePrivilege.objects.filter(site__in=sites)
698 emails = [site_privilege.user.email for site_privilege in site_privileges]
699 users = User.objects.filter(email__in=emails)
700 kwargs['queryset'] = users
702 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
704 def queryset(self, request):
705 # admins can see all privileges. Users can only see privileges at sites
706 # where they have the admin role or pi role.
707 qs = super(SitePrivilegeAdmin, self).queryset(request)
708 #if not request.user.is_admin:
709 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
710 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
711 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
712 # sites = Site.objects.filter(login_base__in=login_bases)
713 # qs = qs.filter(site__in=sites)
716 class SliceForm(forms.ModelForm):
720 'service': LinkedSelect
724 cleaned_data = super(SliceForm, self).clean()
725 name = cleaned_data.get('name')
726 site_id = cleaned_data.get('site')
727 site = Slice.objects.get(id=site_id)
728 if not name.startswith(site.login_base):
729 raise forms.ValidationError('slice name must begin with %s' % site.login_base)
732 class SliceAdmin(PlanetStackBaseAdmin):
734 fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
735 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
736 readonly_fields = ('backend_status_text', )
737 list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_slivers')
738 list_display_links = ('backend_status_icon', 'name', )
739 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
741 user_readonly_fields = fieldList
743 suit_form_tabs =(('general', 'Slice Details'),
744 ('slicenetworks','Networks'),
745 ('sliceprivileges','Privileges'),
746 ('slivers','Slivers'),
748 ('reservations','Reservations'),
751 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
752 deployment_nodes = []
753 for node in Node.objects.all():
754 deployment_nodes.append( (node.deployment.id, node.id, node.name) )
756 deployment_flavors = []
757 for flavor in Flavor.objects.all():
758 for deployment in flavor.deployments.all():
759 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
761 deployment_images = []
762 for image in Image.objects.all():
763 for imageDeployment in image.imagedeployments_set.all():
764 deployment_images.append( (imageDeployment.deployment.id, image.id, image.name) )
766 site_login_bases = []
767 for site in Site.objects.all():
768 site_login_bases.append((site.id, site.login_base))
770 context["deployment_nodes"] = deployment_nodes
771 context["deployment_flavors"] = deployment_flavors
772 context["deployment_images"] = deployment_images
773 context["site_login_bases"] = site_login_bases
774 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
776 def formfield_for_foreignkey(self, db_field, request, **kwargs):
777 if db_field.name == 'site':
778 kwargs['queryset'] = Site.select_by_user(request.user)
779 kwargs['widget'] = forms.Select(attrs={'onChange': "update_slice_prefix(this, $($(this).closest('fieldset')[0]).find('.field-name input')[0].id)"})
781 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
783 def queryset(self, request):
784 # admins can see all keys. Users can only see slices they belong to.
785 return Slice.select_by_user(request.user)
787 def get_formsets(self, request, obj=None):
788 for inline in self.get_inline_instances(request, obj):
789 # hide MyInline in the add view
792 if isinstance(inline, SliverInline):
793 inline.model.caller = request.user
794 yield inline.get_formset(request, obj)
797 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
799 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
801 readonly_fields = ('backend_status_text', )
802 list_display = ('backend_status_icon', 'user', 'slice', 'role')
803 list_display_links = list_display
805 user_readonly_fields = ['user', 'slice', 'role']
806 user_readonly_inlines = []
808 def formfield_for_foreignkey(self, db_field, request, **kwargs):
809 if db_field.name == 'slice':
810 kwargs['queryset'] = Slice.select_by_user(request.user)
812 if db_field.name == 'user':
813 kwargs['queryset'] = User.select_by_user(request.user)
815 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
817 def queryset(self, request):
818 # admins can see all memberships. Users can only see memberships of
819 # slices where they have the admin role.
820 return SlicePrivilege.select_by_user(request.user)
822 def save_model(self, request, obj, form, change):
823 # update openstack connection to use this site/tenant
824 auth = request.session.get('auth', {})
825 auth['tenant'] = obj.slice.slicename
826 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
829 def delete_model(self, request, obj):
830 # update openstack connection to use this site/tenant
831 auth = request.session.get('auth', {})
832 auth['tenant'] = obj.slice.slicename
833 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
837 class ImageAdmin(PlanetStackBaseAdmin):
839 fieldsets = [('Image Details',
840 {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
841 'classes': ['suit-tab suit-tab-general']})
843 readonly_fields = ('backend_status_text', )
845 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
847 inlines = [SliverInline, ImageDeploymentsInline]
849 user_readonly_fields = ['name', 'disk_format', 'container_format']
851 list_display = ['backend_status_icon', 'name']
852 list_display_links = ('backend_status_icon', 'name', )
854 class NodeForm(forms.ModelForm):
857 'site': LinkedSelect,
858 'deployment': LinkedSelect
861 class NodeAdmin(PlanetStackBaseAdmin):
863 list_display = ('backend_status_icon', 'name', 'site', 'deployment')
864 list_display_links = ('backend_status_icon', 'name', )
865 list_filter = ('deployment',)
867 inlines = [TagInline,SliverInline]
868 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
869 readonly_fields = ('backend_status_text', )
871 user_readonly_fields = ['name','site','deployment']
872 user_readonly_inlines = [TagInline,SliverInline]
874 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
877 class SliverForm(forms.ModelForm):
880 ip = forms.CharField(widget=PlainTextWidget)
881 instance_name = forms.CharField(widget=PlainTextWidget)
883 'ip': PlainTextWidget(),
884 'instance_name': PlainTextWidget(),
885 'slice': LinkedSelect,
886 'deploymentNetwork': LinkedSelect,
887 'node': LinkedSelect,
888 'image': LinkedSelect
891 class TagAdmin(PlanetStackBaseAdmin):
892 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
893 list_display_links = list_display
894 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
895 user_readonly_inlines = []
897 class SliverAdmin(PlanetStackBaseAdmin):
900 ('Sliver Details', {'fields': ['backend_status_text', 'slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
902 readonly_fields = ('backend_status_text', )
903 list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'flavor', 'image', 'node', 'deploymentNetwork']
904 list_display_links = ('backend_status_icon', 'ip',)
906 suit_form_tabs =(('general', 'Sliver Details'),
910 inlines = [TagInline]
912 user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image']
914 def formfield_for_foreignkey(self, db_field, request, **kwargs):
915 if db_field.name == 'slice':
916 kwargs['queryset'] = Slice.select_by_user(request.user)
918 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
920 def queryset(self, request):
921 # admins can see all slivers. Users can only see slivers of
922 # the slices they belong to.
923 return Sliver.select_by_user(request.user)
926 def get_formsets(self, request, obj=None):
927 # make some fields read only if we are updating an existing record
929 #self.readonly_fields = ('ip', 'instance_name')
930 self.readonly_fields = ('backend_status_text')
932 self.readonly_fields = ('backend_status_text')
933 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
935 for inline in self.get_inline_instances(request, obj):
936 # hide MyInline in the add view
939 if isinstance(inline, SliverInline):
940 inline.model.caller = request.user
941 yield inline.get_formset(request, obj)
943 #def save_model(self, request, obj, form, change):
944 # # update openstack connection to use this site/tenant
945 # auth = request.session.get('auth', {})
946 # auth['tenant'] = obj.slice.name
947 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
948 # obj.creator = request.user
951 #def delete_model(self, request, obj):
952 # # update openstack connection to use this site/tenant
953 # auth = request.session.get('auth', {})
954 # auth['tenant'] = obj.slice.name
955 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
958 class UserCreationForm(forms.ModelForm):
959 """A form for creating new users. Includes all the required
960 fields, plus a repeated password."""
961 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
962 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
966 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
968 def clean_password2(self):
969 # Check that the two password entries match
970 password1 = self.cleaned_data.get("password1")
971 password2 = self.cleaned_data.get("password2")
972 if password1 and password2 and password1 != password2:
973 raise forms.ValidationError("Passwords don't match")
976 def save(self, commit=True):
977 # Save the provided password in hashed format
978 user = super(UserCreationForm, self).save(commit=False)
979 user.password = self.cleaned_data["password1"]
980 #user.set_password(self.cleaned_data["password1"])
986 class UserChangeForm(forms.ModelForm):
987 """A form for updating users. Includes all the fields on
988 the user, but replaces the password field with admin's
989 password hash display field.
991 password = ReadOnlyPasswordHashField(label='Password',
992 help_text= '<a href=\"password/\">Change Password</a>.')
997 def clean_password(self):
998 # Regardless of what the user provides, return the initial value.
999 # This is done here, rather than on the field, because the
1000 # field does not have access to the initial value
1001 return self.initial["password"]
1003 class UserDashboardViewInline(PlStackTabularInline):
1004 model = UserDashboardView
1006 suit_classes = 'suit-tab suit-tab-dashboards'
1007 fields = ['user', 'dashboardView', 'order']
1009 class UserAdmin(PermissionCheckingAdminMixin, UserAdmin):
1010 # Note: Make sure PermissionCheckingAdminMixin is listed before
1011 # admin.ModelAdmin in the class declaration.
1016 # The forms to add and change user instances
1017 form = UserChangeForm
1018 add_form = UserCreationForm
1020 # The fields to be used in displaying the User model.
1021 # These override the definitions on the base UserAdmin
1022 # that reference specific fields on auth.User.
1023 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
1024 list_filter = ('site',)
1025 inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
1027 fieldListLoginDetails = ['backend_status_text', 'email','site','password','is_active','is_readonly','is_admin','public_key']
1028 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1031 ('Login Details', {'fields': ['backend_status_text', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1032 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1033 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1034 #('Important dates', {'fields': ('last_login',)}),
1038 'classes': ('wide',),
1039 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
1042 readonly_fields = ('backend_status_text', )
1043 search_fields = ('email',)
1044 ordering = ('email',)
1045 filter_horizontal = ()
1047 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1049 suit_form_tabs =(('general','Login Details'),
1050 ('contact','Contact Information'),
1051 ('sliceprivileges','Slice Privileges'),
1052 ('siteprivileges','Site Privileges'),
1053 ('deploymentprivileges','Deployment Privileges'),
1054 ('dashboards','Dashboard Views'))
1056 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1057 if db_field.name == 'site':
1058 kwargs['queryset'] = Site.select_by_user(request.user)
1060 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1062 def queryset(self, request):
1063 return User.select_by_user(request.user)
1065 class DashboardViewAdmin(PlanetStackBaseAdmin):
1066 fieldsets = [('Dashboard View Details',
1067 {'fields': ['backend_status_text', 'name', 'url'],
1068 'classes': ['suit-tab suit-tab-general']})
1070 readonly_fields = ('backend_status_text', )
1072 suit_form_tabs =(('general','Dashboard View Details'),)
1074 class ServiceResourceInline(PlStackTabularInline):
1075 model = ServiceResource
1078 class ServiceClassAdmin(PlanetStackBaseAdmin):
1079 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1080 list_display_links = ('backend_status_icon', 'name', )
1081 inlines = [ServiceResourceInline]
1083 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1084 user_readonly_inlines = []
1086 class ReservedResourceInline(PlStackTabularInline):
1087 model = ReservedResource
1089 suit_classes = 'suit-tab suit-tab-reservedresources'
1091 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1092 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1094 if db_field.name == 'resource':
1095 # restrict resources to those that the slice's service class allows
1096 if request._slice is not None:
1097 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1098 if len(field.queryset) > 0:
1099 field.initial = field.queryset.all()[0]
1101 field.queryset = field.queryset.none()
\r
1102 elif db_field.name == 'sliver':
\r
1103 # restrict slivers to those that belong to the slice
\r
1104 if request._slice is not None:
\r
1105 field.queryset = field.queryset.filter(slice = request._slice)
1107 field.queryset = field.queryset.none()
\r
1111 def queryset(self, request):
1112 return ReservedResource.select_by_user(request.user)
1114 class ReservationChangeForm(forms.ModelForm):
1118 'slice' : LinkedSelect
1121 class ReservationAddForm(forms.ModelForm):
1122 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1123 refresh = forms.CharField(widget=forms.HiddenInput())
1126 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1128 def clean_slice(self):
1129 slice = self.cleaned_data.get("slice")
1130 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1132 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1138 'slice' : LinkedSelect
1142 class ReservationAddRefreshForm(ReservationAddForm):
1143 """ This form is displayed when the Reservation Form receives an update
1144 from the Slice dropdown onChange handler. It doesn't validate the
1145 data and doesn't save the data. This will cause the form to be
1149 """ don't validate anything other than slice """
1150 dont_validate_fields = ("startTime", "duration")
1152 def full_clean(self):
1153 result = super(ReservationAddForm, self).full_clean()
1155 for fieldname in self.dont_validate_fields:
1156 if fieldname in self._errors:
1157 del self._errors[fieldname]
1161 """ don't save anything """
1165 class ReservationAdmin(PlanetStackBaseAdmin):
1166 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
1167 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1168 readonly_fields = ('backend_status_text', )
1169 list_display = ('startTime', 'duration')
1170 form = ReservationAddForm
1172 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1174 inlines = [ReservedResourceInline]
1175 user_readonly_fields = fieldList
1177 def add_view(self, request, form_url='', extra_context=None):
1178 timezone.activate(request.user.timezone)
1179 request._refresh = False
1180 request._slice = None
1181 if request.method == 'POST':
1182 # "refresh" will be set to "1" if the form was submitted due to
1183 # a change in the Slice dropdown.
1184 if request.POST.get("refresh","1") == "1":
1185 request._refresh = True
1186 request.POST["refresh"] = "0"
1188 # Keep track of the slice that was selected, so the
1189 # reservedResource inline can filter items for the slice.
1190 request._slice = request.POST.get("slice",None)
1191 if (request._slice is not None):
1192 request._slice = Slice.objects.get(id=request._slice)
1194 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1197 def changelist_view(self, request, extra_context = None):
1198 timezone.activate(request.user.timezone)
1199 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1201 def get_form(self, request, obj=None, **kwargs):
1204 # For changes, set request._slice to the slice already set in the
1206 request._slice = obj.slice
1207 self.form = ReservationChangeForm
1209 if getattr(request, "_refresh", False):
1210 self.form = ReservationAddRefreshForm
1212 self.form = ReservationAddForm
1213 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1215 def get_readonly_fields(self, request, obj=None):
1216 if (obj is not None):
1217 # Prevent slice from being changed after the reservation has been
1223 def queryset(self, request):
1224 return Reservation.select_by_user(request.user)
1226 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1227 list_display = ("backend_status_icon", "name", )
1228 list_display_links = ('backend_status_icon', 'name', )
1229 user_readonly_fields = ['name']
1230 user_readonly_inlines = []
1232 class RouterAdmin(PlanetStackBaseAdmin):
1233 list_display = ("backend_status_icon", "name", )
1234 list_display_links = ('backend_status_icon', 'name', )
1235 user_readonly_fields = ['name']
1236 user_readonly_inlines = []
1238 class RouterInline(PlStackTabularInline):
1239 model = Router.networks.through
1241 verbose_name_plural = "Routers"
1242 verbose_name = "Router"
1243 suit_classes = 'suit-tab suit-tab-routers'
1245 class NetworkParameterInline(PlStackGenericTabularInline):
1246 model = NetworkParameter
1248 verbose_name_plural = "Parameters"
1249 verbose_name = "Parameter"
1250 suit_classes = 'suit-tab suit-tab-netparams'
1251 fields = ['backend_status_icon', 'parameter', 'value']
1252 readonly_fields = ('backend_status_icon', )
1254 class NetworkSliversInline(PlStackTabularInline):
1255 fields = ['backend_status_icon', 'network','sliver','ip']
1256 readonly_fields = ("backend_status_icon", "ip", )
1257 model = NetworkSliver
1258 selflink_fieldname = "sliver"
1260 verbose_name_plural = "Slivers"
1261 verbose_name = "Sliver"
1262 suit_classes = 'suit-tab suit-tab-networkslivers'
1264 class NetworkSlicesInline(PlStackTabularInline):
1265 model = NetworkSlice
1266 selflink_fieldname = "slice"
1268 verbose_name_plural = "Slices"
1269 verbose_name = "Slice"
1270 suit_classes = 'suit-tab suit-tab-networkslices'
1271 fields = ['backend_status_icon', 'network','slice']
1272 readonly_fields = ('backend_status_icon', )
1274 class NetworkAdmin(PlanetStackBaseAdmin):
1275 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1276 list_display_links = ('backend_status_icon', 'name', )
1277 readonly_fields = ("subnet", )
1279 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1282 (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']}),]
1284 readonly_fields = ('backend_status_text', )
1285 user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1288 ('general','Network Details'),
1289 ('netparams', 'Parameters'),
1290 ('networkslivers','Slivers'),
1291 ('networkslices','Slices'),
1292 ('routers','Routers'),
1294 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1295 list_display = ("backend_status_icon", "name", "guaranteedBandwidth", "visibility")
1296 list_display_links = ('backend_status_icon', 'name', )
1297 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1298 user_readonly_inlines = []
1300 class FlavorAdmin(PlanetStackBaseAdmin):
1301 list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1302 list_display_links = ("backend_status_icon", "name")
1303 user_readonly_fields = ("name", "flavor")
1304 fields = ("name", "description", "flavor", "order", "default")
1306 # register a signal that caches the user's credentials when they log in
1307 def cache_credentials(sender, user, request, **kwds):
1308 auth = {'username': request.POST['username'],
1309 'password': request.POST['password']}
1310 request.session['auth'] = auth
1311 user_logged_in.connect(cache_credentials)
1313 def dollar_field(fieldName, short_description):
1314 def newFunc(self, obj):
1316 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1318 x=getattr(obj, fieldName, 0.0)
1320 newFunc.short_description = short_description
1323 def right_dollar_field(fieldName, short_description):
1324 def newFunc(self, obj):
1326 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1327 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1329 x=getattr(obj, fieldName, 0.0)
1331 newFunc.short_description = short_description
1332 newFunc.allow_tags = True
1335 class InvoiceChargeInline(PlStackTabularInline):
1338 verbose_name_plural = "Charges"
1339 verbose_name = "Charge"
1340 exclude = ['account']
1341 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1342 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1346 dollar_amount = right_dollar_field("amount", "Amount")
1348 class InvoiceAdmin(admin.ModelAdmin):
1349 list_display = ("date", "account")
1351 inlines = [InvoiceChargeInline]
1353 fields = ["date", "account", "dollar_amount"]
1354 readonly_fields = ["date", "account", "dollar_amount"]
1356 dollar_amount = dollar_field("amount", "Amount")
1358 class InvoiceInline(PlStackTabularInline):
1361 verbose_name_plural = "Invoices"
1362 verbose_name = "Invoice"
1363 fields = ["date", "dollar_amount"]
1364 readonly_fields = ["date", "dollar_amount"]
1365 suit_classes = 'suit-tab suit-tab-accountinvoice'
1369 dollar_amount = right_dollar_field("amount", "Amount")
1371 class PendingChargeInline(PlStackTabularInline):
1374 verbose_name_plural = "Charges"
1375 verbose_name = "Charge"
1376 exclude = ["invoice"]
1377 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1378 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1379 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1383 def queryset(self, request):
1384 qs = super(PendingChargeInline, self).queryset(request)
1385 qs = qs.filter(state="pending")
1388 dollar_amount = right_dollar_field("amount", "Amount")
1390 class PaymentInline(PlStackTabularInline):
1393 verbose_name_plural = "Payments"
1394 verbose_name = "Payment"
1395 fields = ["date", "dollar_amount"]
1396 readonly_fields = ["date", "dollar_amount"]
1397 suit_classes = 'suit-tab suit-tab-accountpayments'
1401 dollar_amount = right_dollar_field("amount", "Amount")
1403 class AccountAdmin(admin.ModelAdmin):
1404 list_display = ("site", "balance_due")
1406 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1409 (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1411 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1414 ('general','Account Details'),
1415 ('accountinvoice', 'Invoices'),
1416 ('accountpayments', 'Payments'),
1417 ('accountpendingcharges','Pending Charges'),
1420 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1421 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1422 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1424 # Now register the new UserAdmin...
1425 admin.site.register(User, UserAdmin)
1426 # ... and, since we're not using Django's builtin permissions,
1427 # unregister the Group model from admin.
1428 #admin.site.unregister(Group)
1430 #Do not show django evolution in the admin interface
1431 from django_evolution.models import Version, Evolution
1432 #admin.site.unregister(Version)
1433 #admin.site.unregister(Evolution)
1436 # When debugging it is often easier to see all the classes, but for regular use
1437 # only the top-levels should be displayed
1440 admin.site.register(Deployment, DeploymentAdmin)
1441 admin.site.register(Site, SiteAdmin)
1442 admin.site.register(Slice, SliceAdmin)
1443 admin.site.register(Service, ServiceAdmin)
1444 admin.site.register(Reservation, ReservationAdmin)
1445 admin.site.register(Network, NetworkAdmin)
1446 admin.site.register(Router, RouterAdmin)
1447 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1448 admin.site.register(Account, AccountAdmin)
1449 admin.site.register(Invoice, InvoiceAdmin)
1452 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1453 admin.site.register(ServiceClass, ServiceClassAdmin)
1454 #admin.site.register(PlanetStack)
1455 admin.site.register(Tag, TagAdmin)
1456 admin.site.register(DeploymentRole)
1457 admin.site.register(SiteRole)
1458 admin.site.register(SliceRole)
1459 admin.site.register(PlanetStackRole)
1460 admin.site.register(Node, NodeAdmin)
1461 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1462 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1463 admin.site.register(Sliver, SliverAdmin)
1464 admin.site.register(Image, ImageAdmin)
1465 admin.site.register(DashboardView, DashboardViewAdmin)
1466 admin.site.register(Flavor, FlavorAdmin)