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