1 from core.models import Site
2 from core.models import *
3 from openstack.manager import OpenStackManager
5 from django.contrib import admin
6 from django.contrib.auth.models import Group
7 from django import forms
8 from django.utils.safestring import mark_safe
9 from django.contrib.auth.admin import UserAdmin
10 from django.contrib.admin.widgets import FilteredSelectMultiple
11 from django.contrib.auth.forms import ReadOnlyPasswordHashField
12 from django.contrib.auth.signals import user_logged_in
13 from django.utils import timezone
14 from django.contrib.contenttypes import generic
15 from suit.widgets import LinkedSelect
16 from django.core.exceptions import PermissionDenied
17 from django.core.urlresolvers import reverse, NoReverseMatch
19 import django_evolution
21 def backend_icon(obj): # backend_status, enacted, updated):
22 #return "%s %s %s" % (str(obj.updated), str(obj.enacted), str(obj.backend_status))
23 if (obj.enacted is not None) and obj.enacted >= obj.updated:
24 return '<img src="/static/admin/img/icon_success.gif">'
26 if obj.backend_status == "Provisioning in progress" or obj.backend_status=="":
27 return '<span title="%s"><img src="/static/admin/img/icon_clock.gif"></span>' % obj.backend_status
29 return '<span title="%s"><img src="/static/admin/img/icon_error.gif"></span>' % obj.backend_status
31 def backend_text(obj):
32 icon = backend_icon(obj)
33 if (obj.enacted is not None) and obj.enacted >= obj.updated:
34 return "%s %s" % (icon, "successfully enacted") # enacted on %s" % str(obj.enacted))
36 return "%s %s" % (icon, obj.backend_status)
38 class PlainTextWidget(forms.HiddenInput):
41 def render(self, name, value, attrs=None):
44 return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
46 class ReadOnlyAwareAdmin(admin.ModelAdmin):
48 def has_add_permission(self, request, obj=None):
49 return (not self.__user_is_readonly(request))
51 def has_delete_permission(self, request, obj=None):
52 return (not self.__user_is_readonly(request))
54 def save_model(self, request, obj, form, change):
55 if self.__user_is_readonly(request):
56 raise PermissionDenied
59 return super(ReadOnlyAwareAdmin, self).save_model(request, obj, form, change)
61 def get_actions(self,request):
62 actions = super(ReadOnlyAwareAdmin,self).get_actions(request)
64 if self.__user_is_readonly(request):
65 if 'delete_selected' in actions:
66 del actions['delete_selected']
70 def change_view(self,request,object_id, extra_context=None):
71 if self.__user_is_readonly(request):
72 if not hasattr(self, "readonly_save"):
\r
73 # save the original readonly fields
\r
74 self.readonly_save = self.readonly_fields
\r
75 self.inlines_save = self.inlines
\r
76 if hasattr(self, "user_readonly_fields"):
\r
77 self.readonly_fields=self.user_readonly_fields
\r
78 if hasattr(self, "user_readonly_inlines"):
\r
79 self.inlines = self.user_readonly_inlines
\r
81 if hasattr(self, "readonly_save"):
\r
82 # restore the original readonly fields
\r
83 self.readonly_fields = self.readonly_save
\r
84 if hasattr(self, "inlines_save"):
\r
85 self.inlines = self.inlines_save
88 return super(ReadOnlyAwareAdmin, self).change_view(request, object_id, extra_context=extra_context)
89 except PermissionDenied:
91 if request.method == 'POST':
92 raise PermissionDenied
93 request.readonly = True
94 return super(ReadOnlyAwareAdmin, self).change_view(request, object_id, extra_context=extra_context)
96 def __user_is_readonly(self, request):
97 return request.user.isReadOnlyUser()
99 def backend_status_text(self, obj):
100 return mark_safe(backend_text(obj))
102 def backend_status_icon(self, obj):
103 return mark_safe(backend_icon(obj))
104 backend_status_icon.short_description = ""
107 class SingletonAdmin (ReadOnlyAwareAdmin):
108 def has_add_permission(self, request):
109 if not super(SingletonAdmin, self).has_add_permission(request):
112 num_objects = self.model.objects.count()
119 class PlStackTabularInline(admin.TabularInline):
120 def __init__(self, *args, **kwargs):
121 super(PlStackTabularInline, self).__init__(*args, **kwargs)
123 # InlineModelAdmin as no get_fields() method, so in order to add
124 # the selflink field, we override __init__ to modify self.fields and
125 # self.readonly_fields.
127 self.setup_selflink()
129 def get_change_url(self, model, id):
130 """ Get the URL to a change form in the admin for this model """
131 reverse_path = "admin:%s_change" % (model._meta.db_table)
133 url = reverse(reverse_path, args=(id,))
134 except NoReverseMatch:
139 def setup_selflink(self):
140 if hasattr(self, "selflink_fieldname"):
141 """ self.selflink_model can be defined to punch through a relation
142 to its target object. For example, in SliceNetworkInline, set
143 selflink_model = "network", and the URL will lead to the Network
144 object instead of trying to bring up a change view of the
147 self.selflink_model = getattr(self.model,self.selflink_fieldname).field.rel.to
149 self.selflink_model = self.model
151 url = self.get_change_url(self.selflink_model, 0)
153 # We don't have an admin for this object, so don't create the
158 # Since we need to add "selflink" to the field list, we need to create
159 # self.fields if it is None.
160 if (self.fields is None):
162 for f in self.model._meta.fields:
163 if f.editable and f.name != "id":
164 self.fields.append(f.name)
166 self.fields = tuple(self.fields) + ("selflink", )
168 if self.readonly_fields is None:
169 self.readonly_fields = ()
171 self.readonly_fields = tuple(self.readonly_fields) + ("selflink", )
173 def selflink(self, obj):
174 if hasattr(self, "selflink_fieldname"):
175 obj = getattr(obj, self.selflink_fieldname)
178 url = self.get_change_url(self.selflink_model, obj.id)
179 return "<a href='%s'>Details</a>" % str(url)
181 return "Not present"
\r
183 selflink.allow_tags = True
184 selflink.short_description = "Details"
186 def has_add_permission(self, request):
187 return not request.user.isReadOnlyUser()
189 def get_readonly_fields(self, request, obj=None):
190 readonly_fields = list(self.readonly_fields)[:]
191 if request.user.isReadOnlyUser():
192 for field in self.fields:
193 if not field in readonly_fields:
194 readonly_fields.append(field)
195 return readonly_fields
197 def backend_status_icon(self, obj):
198 return mark_safe(backend_icon(obj))
199 backend_status_icon.short_description = ""
201 class PlStackGenericTabularInline(generic.GenericTabularInline):
202 def has_add_permission(self, request):
203 return not request.user.isReadOnlyUser()
205 def get_readonly_fields(self, request, obj=None):
206 readonly_fields = list(self.readonly_fields)[:]
207 if request.user.isReadOnlyUser():
208 for field in self.fields:
209 if not field in readonly_fields:
210 readonly_fields.append(field)
211 return readonly_fields
213 def backend_status_icon(self, obj):
214 return mark_safe(backend_icon(obj))
215 backend_status_icon.short_description = ""
217 class ReservationInline(PlStackTabularInline):
220 suit_classes = 'suit-tab suit-tab-reservations'
222 def queryset(self, request):
223 return Reservation.select_by_user(request.user)
225 class TagInline(PlStackGenericTabularInline):
228 suit_classes = 'suit-tab suit-tab-tags'
229 fields = ['service', 'name', 'value']
231 def queryset(self, request):
232 return Tag.select_by_user(request.user)
234 class NetworkLookerUpper:
235 """ This is a callable that looks up a network name in a sliver and returns
236 the ip address for that network.
239 byNetworkName = {} # class variable
241 def __init__(self, name):
242 self.short_description = name
244 self.network_name = name
246 def __call__(self, obj):
248 for nbs in obj.networksliver_set.all():
249 if (nbs.network.name == self.network_name):
254 return self.network_name
257 def get(network_name):
258 """ We want to make sure we alwars return the same NetworkLookerUpper
259 because sometimes django will cause them to be instantiated multiple
260 times (and we don't want different ones in form.fields vs
261 SliverInline.readonly_fields).
263 if network_name not in NetworkLookerUpper.byNetworkName:
264 NetworkLookerUpper.byNetworkName[network_name] = NetworkLookerUpper(network_name)
265 return NetworkLookerUpper.byNetworkName[network_name]
267 class SliverInline(PlStackTabularInline):
269 fields = ['backend_status_icon', 'all_ips_string', 'instance_name', 'slice', '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);"})
282 field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
287 SMBAKER: This is the old code that implemented each network type as a
288 separate column in the sliver table.
290 def _declared_fieldsets(self):
291 # Return None so django will call get_fieldsets and we can insert our
295 def get_readonly_fields(self, request, obj=None):
296 readonly_fields = list(super(SliverInline, self).get_readonly_fields(request, obj))
298 # Lookup the networks that are bound to the slivers, and add those
299 # network names to the list of readonly fields.
301 for sliver in obj.slivers.all():
302 for nbs in sliver.networksliver_set.all():
304 network_name = nbs.network.name
305 if network_name not in [str(x) for x in readonly_fields]:
306 readonly_fields.append(NetworkLookerUpper.get(network_name))
308 return readonly_fields
310 def get_fieldsets(self, request, obj=None):
311 form = self.get_formset(request, obj).form
312 # fields = the read/write files + the read-only fields
313 fields = list(self.fields)
314 for fieldName in self.get_readonly_fields(request,obj):
315 if not fieldName in fields:
316 fields.append(fieldName)
318 return [(None, {'fields': fields})]
321 class SiteInline(PlStackTabularInline):
324 suit_classes = 'suit-tab suit-tab-sites'
326 def queryset(self, request):
327 return Site.select_by_user(request.user)
329 class UserInline(PlStackTabularInline):
331 fields = ['backend_status_icon', 'email', 'firstname', 'lastname']
332 readonly_fields = ('backend_status_icon', )
334 suit_classes = 'suit-tab suit-tab-users'
336 def queryset(self, request):
337 return User.select_by_user(request.user)
339 class SliceInline(PlStackTabularInline):
341 fields = ['backend_status_icon', 'name', 'site', 'serviceClass', 'service']
342 readonly_fields = ('backend_status_icon', )
344 suit_classes = 'suit-tab suit-tab-slices'
346 def queryset(self, request):
347 return Slice.select_by_user(request.user)
349 class NodeInline(PlStackTabularInline):
352 suit_classes = 'suit-tab suit-tab-nodes'
353 fields = ['backend_status_icon', 'name','deployment','site']
354 readonly_fields = ('backend_status_icon', )
356 class DeploymentPrivilegeInline(PlStackTabularInline):
357 model = DeploymentPrivilege
359 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
360 fields = ['backend_status_icon', 'user','role','deployment']
361 readonly_fields = ('backend_status_icon', )
363 def queryset(self, request):
364 return DeploymentPrivilege.select_by_user(request.user)
366 class SitePrivilegeInline(PlStackTabularInline):
367 model = SitePrivilege
369 suit_classes = 'suit-tab suit-tab-siteprivileges'
370 fields = ['backend_status_icon', 'user','site', 'role']
371 readonly_fields = ('backend_status_icon', )
373 def formfield_for_foreignkey(self, db_field, request, **kwargs):
374 if db_field.name == 'site':
375 kwargs['queryset'] = Site.select_by_user(request.user)
377 if db_field.name == 'user':
378 kwargs['queryset'] = User.select_by_user(request.user)
379 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
381 def queryset(self, request):
382 return SitePrivilege.select_by_user(request.user)
384 class SiteDeploymentInline(PlStackTabularInline):
385 model = SiteDeployments
387 suit_classes = 'suit-tab suit-tab-deployments'
388 fields = ['backend_status_icon', 'deployment','site']
389 readonly_fields = ('backend_status_icon', )
391 def formfield_for_foreignkey(self, db_field, request, **kwargs):
392 if db_field.name == 'site':
393 kwargs['queryset'] = Site.select_by_user(request.user)
395 if db_field.name == 'deployment':
396 kwargs['queryset'] = Deployment.select_by_user(request.user)
397 return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
399 def queryset(self, request):
400 return SiteDeployments.select_by_user(request.user)
403 class SlicePrivilegeInline(PlStackTabularInline):
404 model = SlicePrivilege
405 suit_classes = 'suit-tab suit-tab-sliceprivileges'
407 fields = ('backend_status_icon', 'user', 'slice', 'role')
408 readonly_fields = ('backend_status_icon', )
410 def formfield_for_foreignkey(self, db_field, request, **kwargs):
411 if db_field.name == 'slice':
412 kwargs['queryset'] = Slice.select_by_user(request.user)
413 if db_field.name == 'user':
414 kwargs['queryset'] = User.select_by_user(request.user)
416 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
418 def queryset(self, request):
419 return SlicePrivilege.select_by_user(request.user)
421 class SliceNetworkInline(PlStackTabularInline):
422 model = Network.slices.through
423 selflink_fieldname = "network"
425 verbose_name = "Network Connection"
426 verbose_name_plural = "Network Connections"
427 suit_classes = 'suit-tab suit-tab-slicenetworks'
428 fields = ['backend_status_icon', 'network']
429 readonly_fields = ('backend_status_icon', )
431 class ImageDeploymentsInline(PlStackTabularInline):
432 model = ImageDeployments
434 verbose_name = "Image Deployments"
435 verbose_name_plural = "Image Deployments"
436 suit_classes = 'suit-tab suit-tab-imagedeployments'
437 fields = ['backend_status_icon', 'image', 'deployment', 'glance_image_id']
438 readonly_fields = ['backend_status_icon', 'glance_image_id']
440 class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
443 def save_model(self, request, obj, form, change):
444 obj.caller = request.user
445 # update openstack connection to use this site/tenant
446 obj.save_by_user(request.user)
448 def delete_model(self, request, obj):
449 obj.delete_by_user(request.user)
451 def save_formset(self, request, form, formset, change):
452 instances = formset.save(commit=False)
453 for instance in instances:
454 instance.save_by_user(request.user)
457 class SliceRoleAdmin(PlanetStackBaseAdmin):
461 class SiteRoleAdmin(PlanetStackBaseAdmin):
465 class DeploymentAdminForm(forms.ModelForm):
466 sites = forms.ModelMultipleChoiceField(
467 queryset=Site.objects.all(),
469 help_text="Select which sites are allowed to host nodes in this deployment",
470 widget=FilteredSelectMultiple(
471 verbose_name=('Sites'), is_stacked=False
474 images = forms.ModelMultipleChoiceField(
475 queryset=Image.objects.all(),
477 help_text="Select which images should be deployed on this deployment",
478 widget=FilteredSelectMultiple(
479 verbose_name=('Images'), is_stacked=False
482 flavors = forms.ModelMultipleChoiceField(
483 queryset=Flavor.objects.all(),
485 help_text="Select which flavors should be usable on this deployment",
486 widget=FilteredSelectMultiple(
487 verbose_name=('Flavors'), is_stacked=False
492 many_to_many = ["flavors",]
494 def __init__(self, *args, **kwargs):
495 request = kwargs.pop('request', None)
496 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
498 self.fields['accessControl'].initial = "allow site " + request.user.site.name
500 if self.instance and self.instance.pk:
501 self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments_set.all()]
502 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments_set.all()]
503 self.fields['flavors'].initial = self.instance.flavors.all()
505 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
506 """ helper function for handling m2m relations from the MultipleChoiceField
508 this_obj: the source object we want to link from
510 selected_objs: a list of destination objects we want to link to
512 all_relations: the full set of relations involving this_obj, including ones we don't want
514 relation_class: the class that implements the relation from source to dest
516 local_attrname: field name representing this_obj in relation_class
518 foreign_attrname: field name representing selected_objs in relation_class
520 This function will remove all newobjclass relations from this_obj
521 that are not contained in selected_objs, and add any relations that
522 are in selected_objs but don't exist in the data model yet.
525 existing_dest_objs = []
526 for relation in list(all_relations):
527 if getattr(relation, foreign_attrname) not in selected_objs:
528 #print "deleting site", sdp.site
531 existing_dest_objs.append(getattr(relation, foreign_attrname))
533 for dest_obj in selected_objs:
534 if dest_obj not in existing_dest_objs:
535 #print "adding site", site
536 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
537 relation = relation_class(**kwargs)
540 def save(self, commit=True):
541 deployment = super(DeploymentAdminForm, self).save(commit=False)
543 deployment.flavors = self.cleaned_data['flavors']
549 # save_m2m() doesn't seem to work with 'through' relations. So we
550 # create/destroy the through models ourselves. There has to be
553 self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments_set.all(), SiteDeployments, "deployment", "site")
554 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments_set.all(), ImageDeployments, "deployment", "image")
560 class DeploymentAdminROForm(DeploymentAdminForm):
561 def save(self, commit=True):
562 raise PermissionDenied
564 class SiteAssocInline(PlStackTabularInline):
565 model = Site.deployments.through
567 suit_classes = 'suit-tab suit-tab-sites'
569 class DeploymentAdmin(PlanetStackBaseAdmin):
571 fieldList = ['backend_status_text', 'name', 'sites', 'images', 'flavors', 'accessControl']
572 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
573 inlines = [DeploymentPrivilegeInline,NodeInline,TagInline] # ,ImageDeploymentsInline]
574 list_display = ['backend_status_icon', 'name']
575 list_display_links = ('backend_status_icon', 'name', )
576 readonly_fields = ('backend_status_text', )
578 user_readonly_fields = ['name']
580 suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags')) # ,('imagedeployments','Images'))
582 def get_form(self, request, obj=None, **kwargs):
583 if request.user.isReadOnlyUser():
584 kwargs["form"] = DeploymentAdminROForm
586 kwargs["form"] = DeploymentAdminForm
587 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
589 # from stackexchange: pass the request object into the form
591 class AdminFormMetaClass(adminForm):
592 def __new__(cls, *args, **kwargs):
593 kwargs['request'] = request
594 return adminForm(*args, **kwargs)
596 return AdminFormMetaClass
598 class ServiceAttrAsTabInline(PlStackTabularInline):
599 model = ServiceAttribute
600 fields = ['name','value']
602 suit_classes = 'suit-tab suit-tab-serviceattrs'
604 class ServiceAdmin(PlanetStackBaseAdmin):
605 list_display = ("backend_status_icon","name","description","versionNumber","enabled","published")
606 list_display_links = ('backend_status_icon', 'name', )
607 fieldList = ["backend_status_text","name","description","versionNumber","enabled","published"]
608 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
609 inlines = [ServiceAttrAsTabInline,SliceInline]
610 readonly_fields = ('backend_status_text', )
612 user_readonly_fields = fieldList
614 suit_form_tabs =(('general', 'Service Details'),
616 ('serviceattrs','Additional Attributes'),
619 class SiteAdmin(PlanetStackBaseAdmin):
620 fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
622 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
623 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
625 suit_form_tabs =(('general', 'Site Details'),
627 ('siteprivileges','Privileges'),
628 ('deployments','Deployments'),
633 readonly_fields = ['backend_status_text', 'accountLink']
635 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
637 list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
638 list_display_links = ('backend_status_icon', 'name', )
639 filter_horizontal = ('deployments',)
640 inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentInline]
641 search_fields = ['name']
643 def queryset(self, request):
644 return Site.select_by_user(request.user)
646 def get_formsets(self, request, obj=None):
647 for inline in self.get_inline_instances(request, obj):
648 # hide MyInline in the add view
651 if isinstance(inline, SliceInline):
652 inline.model.caller = request.user
653 yield inline.get_formset(request, obj)
655 def get_formsets(self, request, obj=None):
656 for inline in self.get_inline_instances(request, obj):
657 # hide MyInline in the add view
660 if isinstance(inline, SliverInline):
661 inline.model.caller = request.user
662 yield inline.get_formset(request, obj)
664 def accountLink(self, obj):
665 link_obj = obj.accounts.all()
667 reverse_path = "admin:core_account_change"
668 url = reverse(reverse_path, args =(link_obj[0].id,))
669 return "<a href='%s'>%s</a>" % (url, "view billing details")
671 return "no billing data for this site"
672 accountLink.allow_tags = True
673 accountLink.short_description = "Billing"
675 def save_model(self, request, obj, form, change):
676 # update openstack connection to use this site/tenant
677 obj.save_by_user(request.user)
679 def delete_model(self, request, obj):
680 obj.delete_by_user(request.user)
683 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
684 fieldList = ['backend_status_text', 'user', 'site', 'role']
686 (None, {'fields': fieldList, 'classes':['collapse']})
688 readonly_fields = ('backend_status_text', )
689 list_display = ('backend_status_icon', 'user', 'site', 'role')
690 list_display_links = list_display
691 user_readonly_fields = fieldList
692 user_readonly_inlines = []
694 def formfield_for_foreignkey(self, db_field, request, **kwargs):
695 if db_field.name == 'site':
696 if not request.user.is_admin:
697 # only show sites where user is an admin or pi
699 for site_privilege in SitePrivilege.objects.filer(user=request.user):
700 if site_privilege.role.role_type in ['admin', 'pi']:
701 sites.add(site_privilege.site)
702 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
704 if db_field.name == 'user':
705 if not request.user.is_admin:
706 # only show users from sites where caller has admin or pi role
707 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
708 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
709 sites = [site_privilege.site for site_privilege in site_privileges]
710 site_privileges = SitePrivilege.objects.filter(site__in=sites)
711 emails = [site_privilege.user.email for site_privilege in site_privileges]
712 users = User.objects.filter(email__in=emails)
713 kwargs['queryset'] = users
715 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
717 def queryset(self, request):
718 # admins can see all privileges. Users can only see privileges at sites
719 # where they have the admin role or pi role.
720 qs = super(SitePrivilegeAdmin, self).queryset(request)
721 #if not request.user.is_admin:
722 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
723 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
724 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
725 # sites = Site.objects.filter(login_base__in=login_bases)
726 # qs = qs.filter(site__in=sites)
729 class SliceForm(forms.ModelForm):
733 'service': LinkedSelect
736 class SliceAdmin(PlanetStackBaseAdmin):
738 fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
739 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
740 readonly_fields = ('backend_status_text', )
741 list_display = ('backend_status_icon', 'slicename', 'site','serviceClass', 'slice_url', 'max_slivers')
742 list_display_links = ('backend_status_icon', 'slicename', )
743 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
745 user_readonly_fields = fieldList
747 suit_form_tabs =(('general', 'Slice Details'),
748 ('slicenetworks','Networks'),
749 ('sliceprivileges','Privileges'),
750 ('slivers','Slivers'),
752 ('reservations','Reservations'),
755 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
756 deployment_nodes = []
757 for node in Node.objects.all():
758 deployment_nodes.append( (node.deployment.id, node.id, node.name) )
760 deployment_flavors = []
761 for flavor in Flavor.objects.all():
762 for deployment in flavor.deployments.all():
763 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
765 site_login_bases = []
766 for site in Site.objects.all():
767 site_login_bases.append((site.id, site.login_base))
769 context["deployment_nodes"] = deployment_nodes
770 context["deployment_flavors"] = deployment_flavors
771 context["site_login_bases"] = site_login_bases
772 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
774 def formfield_for_foreignkey(self, db_field, request, **kwargs):
775 if db_field.name == 'site':
776 kwargs['queryset'] = Site.select_by_user(request.user)
777 kwargs['widget'] = forms.Select(attrs={'onChange': "update_slice_prefix(this, $($(this).closest('fieldset')[0]).find('.field-name input')[0].id)"})
779 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
781 def queryset(self, request):
782 # admins can see all keys. Users can only see slices they belong to.
783 return Slice.select_by_user(request.user)
785 def get_formsets(self, request, obj=None):
786 for inline in self.get_inline_instances(request, obj):
787 # hide MyInline in the add view
790 if isinstance(inline, SliverInline):
791 inline.model.caller = request.user
792 yield inline.get_formset(request, obj)
795 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
797 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
799 readonly_fields = ('backend_status_text', )
800 list_display = ('backend_status_icon', 'user', 'slice', 'role')
801 list_display_links = list_display
803 user_readonly_fields = ['user', 'slice', 'role']
804 user_readonly_inlines = []
806 def formfield_for_foreignkey(self, db_field, request, **kwargs):
807 if db_field.name == 'slice':
808 kwargs['queryset'] = Slice.select_by_user(request.user)
810 if db_field.name == 'user':
811 kwargs['queryset'] = User.select_by_user(request.user)
813 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
815 def queryset(self, request):
816 # admins can see all memberships. Users can only see memberships of
817 # slices where they have the admin role.
818 return SlicePrivilege.select_by_user(request.user)
820 def save_model(self, request, obj, form, change):
821 # update openstack connection to use this site/tenant
822 auth = request.session.get('auth', {})
823 auth['tenant'] = obj.slice.slicename
824 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
827 def delete_model(self, request, obj):
828 # update openstack connection to use this site/tenant
829 auth = request.session.get('auth', {})
830 auth['tenant'] = obj.slice.slicename
831 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
835 class ImageAdmin(PlanetStackBaseAdmin):
837 fieldsets = [('Image Details',
838 {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
839 'classes': ['suit-tab suit-tab-general']})
841 readonly_fields = ('backend_status_text', )
843 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
845 inlines = [SliverInline, ImageDeploymentsInline]
847 user_readonly_fields = ['name', 'disk_format', 'container_format']
849 list_display = ['backend_status_icon', 'name']
850 list_display_links = ('backend_status_icon', 'name', )
852 class NodeForm(forms.ModelForm):
855 'site': LinkedSelect,
856 'deployment': LinkedSelect
859 class NodeAdmin(PlanetStackBaseAdmin):
861 list_display = ('backend_status_icon', 'name', 'site', 'deployment')
862 list_display_links = ('backend_status_icon', 'name', )
863 list_filter = ('deployment',)
865 inlines = [TagInline,SliverInline]
866 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
867 readonly_fields = ('backend_status_text', )
869 user_readonly_fields = ['name','site','deployment']
870 user_readonly_inlines = [TagInline,SliverInline]
872 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
875 class SliverForm(forms.ModelForm):
878 ip = forms.CharField(widget=PlainTextWidget)
879 instance_name = forms.CharField(widget=PlainTextWidget)
881 'ip': PlainTextWidget(),
882 'instance_name': PlainTextWidget(),
883 'slice': LinkedSelect,
884 'deploymentNetwork': LinkedSelect,
885 'node': LinkedSelect,
886 'image': LinkedSelect
889 class TagAdmin(PlanetStackBaseAdmin):
890 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
891 list_display_links = list_display
892 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
893 user_readonly_inlines = []
895 class SliverAdmin(PlanetStackBaseAdmin):
898 ('Sliver Details', {'fields': ['backend_status_text', 'slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
900 readonly_fields = ('backend_status_text', )
901 list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'flavor', 'image', 'node', 'deploymentNetwork']
902 list_display_links = ('backend_status_icon', 'ip',)
904 suit_form_tabs =(('general', 'Sliver Details'),
908 inlines = [TagInline]
910 user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image']
912 def formfield_for_foreignkey(self, db_field, request, **kwargs):
913 if db_field.name == 'slice':
914 kwargs['queryset'] = Slice.select_by_user(request.user)
916 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
918 def queryset(self, request):
919 # admins can see all slivers. Users can only see slivers of
920 # the slices they belong to.
921 return Sliver.select_by_user(request.user)
924 def get_formsets(self, request, obj=None):
925 # make some fields read only if we are updating an existing record
927 #self.readonly_fields = ('ip', 'instance_name')
928 self.readonly_fields = ('backend_status_text')
930 self.readonly_fields = ('backend_status_text')
931 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
933 for inline in self.get_inline_instances(request, obj):
934 # hide MyInline in the add view
937 if isinstance(inline, SliverInline):
938 inline.model.caller = request.user
939 yield inline.get_formset(request, obj)
941 #def save_model(self, request, obj, form, change):
942 # # update openstack connection to use this site/tenant
943 # auth = request.session.get('auth', {})
944 # auth['tenant'] = obj.slice.name
945 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
946 # obj.creator = request.user
949 #def delete_model(self, request, obj):
950 # # update openstack connection to use this site/tenant
951 # auth = request.session.get('auth', {})
952 # auth['tenant'] = obj.slice.name
953 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
956 class UserCreationForm(forms.ModelForm):
957 """A form for creating new users. Includes all the required
958 fields, plus a repeated password."""
959 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
960 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
964 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
966 def clean_password2(self):
967 # Check that the two password entries match
968 password1 = self.cleaned_data.get("password1")
969 password2 = self.cleaned_data.get("password2")
970 if password1 and password2 and password1 != password2:
971 raise forms.ValidationError("Passwords don't match")
974 def save(self, commit=True):
975 # Save the provided password in hashed format
976 user = super(UserCreationForm, self).save(commit=False)
977 user.password = self.cleaned_data["password1"]
978 #user.set_password(self.cleaned_data["password1"])
984 class UserChangeForm(forms.ModelForm):
985 """A form for updating users. Includes all the fields on
986 the user, but replaces the password field with admin's
987 password hash display field.
989 password = ReadOnlyPasswordHashField(label='Password',
990 help_text= '<a href=\"password/\">Change Password</a>.')
995 def clean_password(self):
996 # Regardless of what the user provides, return the initial value.
997 # This is done here, rather than on the field, because the
998 # field does not have access to the initial value
999 return self.initial["password"]
1001 class UserDashboardViewInline(PlStackTabularInline):
1002 model = UserDashboardView
1004 suit_classes = 'suit-tab suit-tab-dashboards'
1005 fields = ['user', 'dashboardView', 'order']
1007 class UserAdmin(UserAdmin):
1011 # The forms to add and change user instances
1012 form = UserChangeForm
1013 add_form = UserCreationForm
1015 # The fields to be used in displaying the User model.
1016 # These override the definitions on the base UserAdmin
1017 # that reference specific fields on auth.User.
1018 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
1019 list_filter = ('site',)
1020 inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
1022 fieldListLoginDetails = ['email','site','password','is_active','is_readonly','is_admin','public_key']
1023 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1026 ('Login Details', {'fields': ['backend_status_text', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1027 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1028 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1029 #('Important dates', {'fields': ('last_login',)}),
1033 'classes': ('wide',),
1034 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
1037 readonly_fields = ('backend_status_text', )
1038 search_fields = ('email',)
1039 ordering = ('email',)
1040 filter_horizontal = ()
1042 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1044 suit_form_tabs =(('general','Login Details'),
1045 ('contact','Contact Information'),
1046 ('sliceprivileges','Slice Privileges'),
1047 ('siteprivileges','Site Privileges'),
1048 ('deploymentprivileges','Deployment Privileges'),
1049 ('dashboards','Dashboard Views'))
1051 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1052 if db_field.name == 'site':
1053 kwargs['queryset'] = Site.select_by_user(request.user)
1055 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1057 def has_add_permission(self, request, obj=None):
1058 return (not self.__user_is_readonly(request))
1060 def has_delete_permission(self, request, obj=None):
1061 return (not self.__user_is_readonly(request))
1063 def get_actions(self,request):
1064 actions = super(UserAdmin,self).get_actions(request)
1066 if self.__user_is_readonly(request):
1067 if 'delete_selected' in actions:
1068 del actions['delete_selected']
1072 def change_view(self,request,object_id, extra_context=None):
1074 if self.__user_is_readonly(request):
1075 if not hasattr(self, "readonly_save"):
1076 # save the original readonly fields
\r
1077 self.readonly_save = self.readonly_fields
\r
1078 self.inlines_save = self.inlines
1079 if hasattr(self, "user_readonly_fields"):
1080 self.readonly_fields=self.user_readonly_fields
1081 if hasattr(self, "user_readonly_inlines"):
1082 self.inlines = self.user_readonly_inlines
1084 if hasattr(self, "readonly_save"):
\r
1085 # restore the original readonly fields
\r
1086 self.readonly_fields = self.readonly_save
\r
1087 self.inlines = self.inlines_save
1090 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1091 except PermissionDenied:
1093 if request.method == 'POST':
1094 raise PermissionDenied
1095 request.readonly = True
1096 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1098 def __user_is_readonly(self, request):
1099 #groups = [x.name for x in request.user.groups.all() ]
1100 #return "readonly" in groups
1101 return request.user.isReadOnlyUser()
1103 def queryset(self, request):
1104 return User.select_by_user(request.user)
1106 def backend_status_text(self, obj):
1107 return mark_safe(backend_text(obj))
1109 def backend_status_icon(self, obj):
1110 return mark_safe(backend_icon(obj))
1111 backend_status_icon.short_description = ""
1113 class DashboardViewAdmin(PlanetStackBaseAdmin):
1114 fieldsets = [('Dashboard View Details',
1115 {'fields': ['backend_status_text', 'name', 'url'],
1116 'classes': ['suit-tab suit-tab-general']})
1118 readonly_fields = ('backend_status_text', )
1120 suit_form_tabs =(('general','Dashboard View Details'),)
1122 class ServiceResourceInline(PlStackTabularInline):
1123 model = ServiceResource
1126 class ServiceClassAdmin(PlanetStackBaseAdmin):
1127 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1128 list_display_links = ('backend_status_icon', 'name', )
1129 inlines = [ServiceResourceInline]
1131 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1132 user_readonly_inlines = []
1134 class ReservedResourceInline(PlStackTabularInline):
1135 model = ReservedResource
1137 suit_classes = 'suit-tab suit-tab-reservedresources'
1139 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1140 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1142 if db_field.name == 'resource':
1143 # restrict resources to those that the slice's service class allows
1144 if request._slice is not None:
1145 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1146 if len(field.queryset) > 0:
1147 field.initial = field.queryset.all()[0]
1149 field.queryset = field.queryset.none()
\r
1150 elif db_field.name == 'sliver':
\r
1151 # restrict slivers to those that belong to the slice
\r
1152 if request._slice is not None:
\r
1153 field.queryset = field.queryset.filter(slice = request._slice)
1155 field.queryset = field.queryset.none()
\r
1159 def queryset(self, request):
1160 return ReservedResource.select_by_user(request.user)
1162 class ReservationChangeForm(forms.ModelForm):
1166 'slice' : LinkedSelect
1169 class ReservationAddForm(forms.ModelForm):
1170 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1171 refresh = forms.CharField(widget=forms.HiddenInput())
1174 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1176 def clean_slice(self):
1177 slice = self.cleaned_data.get("slice")
1178 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1180 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1186 'slice' : LinkedSelect
1190 class ReservationAddRefreshForm(ReservationAddForm):
1191 """ This form is displayed when the Reservation Form receives an update
1192 from the Slice dropdown onChange handler. It doesn't validate the
1193 data and doesn't save the data. This will cause the form to be
1197 """ don't validate anything other than slice """
1198 dont_validate_fields = ("startTime", "duration")
1200 def full_clean(self):
1201 result = super(ReservationAddForm, self).full_clean()
1203 for fieldname in self.dont_validate_fields:
1204 if fieldname in self._errors:
1205 del self._errors[fieldname]
1209 """ don't save anything """
1213 class ReservationAdmin(PlanetStackBaseAdmin):
1214 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
1215 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1216 readonly_fields = ('backend_status_text', )
1217 list_display = ('startTime', 'duration')
1218 form = ReservationAddForm
1220 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1222 inlines = [ReservedResourceInline]
1223 user_readonly_fields = fieldList
1225 def add_view(self, request, form_url='', extra_context=None):
1226 timezone.activate(request.user.timezone)
1227 request._refresh = False
1228 request._slice = None
1229 if request.method == 'POST':
1230 # "refresh" will be set to "1" if the form was submitted due to
1231 # a change in the Slice dropdown.
1232 if request.POST.get("refresh","1") == "1":
1233 request._refresh = True
1234 request.POST["refresh"] = "0"
1236 # Keep track of the slice that was selected, so the
1237 # reservedResource inline can filter items for the slice.
1238 request._slice = request.POST.get("slice",None)
1239 if (request._slice is not None):
1240 request._slice = Slice.objects.get(id=request._slice)
1242 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1245 def changelist_view(self, request, extra_context = None):
1246 timezone.activate(request.user.timezone)
1247 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1249 def get_form(self, request, obj=None, **kwargs):
1252 # For changes, set request._slice to the slice already set in the
1254 request._slice = obj.slice
1255 self.form = ReservationChangeForm
1257 if getattr(request, "_refresh", False):
1258 self.form = ReservationAddRefreshForm
1260 self.form = ReservationAddForm
1261 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1263 def get_readonly_fields(self, request, obj=None):
1264 if (obj is not None):
1265 # Prevent slice from being changed after the reservation has been
1271 def queryset(self, request):
1272 return Reservation.select_by_user(request.user)
1274 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1275 list_display = ("backend_status_icon", "name", )
1276 list_display_links = ('backend_status_icon', 'name', )
1277 user_readonly_fields = ['name']
1278 user_readonly_inlines = []
1280 class RouterAdmin(PlanetStackBaseAdmin):
1281 list_display = ("backend_status_icon", "name", )
1282 list_display_links = ('backend_status_icon', 'name', )
1283 user_readonly_fields = ['name']
1284 user_readonly_inlines = []
1286 class RouterInline(PlStackTabularInline):
1287 model = Router.networks.through
1289 verbose_name_plural = "Routers"
1290 verbose_name = "Router"
1291 suit_classes = 'suit-tab suit-tab-routers'
1293 class NetworkParameterInline(PlStackGenericTabularInline):
1294 model = NetworkParameter
1296 verbose_name_plural = "Parameters"
1297 verbose_name = "Parameter"
1298 suit_classes = 'suit-tab suit-tab-netparams'
1299 fields = ['backend_status_icon', 'parameter', 'value']
1300 readonly_fields = ('backend_status_icon', )
1302 class NetworkSliversInline(PlStackTabularInline):
1303 fields = ['backend_status_icon', 'network','sliver','ip']
1304 readonly_fields = ("backend_status_icon", "ip", )
1305 model = NetworkSliver
1306 selflink_fieldname = "sliver"
1308 verbose_name_plural = "Slivers"
1309 verbose_name = "Sliver"
1310 suit_classes = 'suit-tab suit-tab-networkslivers'
1312 class NetworkSlicesInline(PlStackTabularInline):
1313 model = NetworkSlice
1314 selflink_fieldname = "slice"
1316 verbose_name_plural = "Slices"
1317 verbose_name = "Slice"
1318 suit_classes = 'suit-tab suit-tab-networkslices'
1319 fields = ['backend_status_icon', 'network','slice']
1320 readonly_fields = ('backend_status_icon', )
1322 class NetworkAdmin(PlanetStackBaseAdmin):
1323 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1324 list_display_links = ('backend_status_icon', 'name', )
1325 readonly_fields = ("subnet", )
1327 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1330 (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']}),]
1332 readonly_fields = ('backend_status_text', )
1333 user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1336 ('general','Network Details'),
1337 ('netparams', 'Parameters'),
1338 ('networkslivers','Slivers'),
1339 ('networkslices','Slices'),
1340 ('routers','Routers'),
1342 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1343 list_display = ("backend_status_icon", "name", "guaranteedBandwidth", "visibility")
1344 list_display_links = ('backend_status_icon', 'name', )
1345 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1346 user_readonly_inlines = []
1348 class FlavorAdmin(PlanetStackBaseAdmin):
1349 list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1350 list_display_links = ("backend_status_icon", "name")
1351 user_readonly_fields = ("name", "flavor")
1352 fields = ("name", "description", "flavor", "order", "default")
1354 # register a signal that caches the user's credentials when they log in
1355 def cache_credentials(sender, user, request, **kwds):
1356 auth = {'username': request.POST['username'],
1357 'password': request.POST['password']}
1358 request.session['auth'] = auth
1359 user_logged_in.connect(cache_credentials)
1361 def dollar_field(fieldName, short_description):
1362 def newFunc(self, obj):
1364 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1366 x=getattr(obj, fieldName, 0.0)
1368 newFunc.short_description = short_description
1371 def right_dollar_field(fieldName, short_description):
1372 def newFunc(self, obj):
1374 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1375 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1377 x=getattr(obj, fieldName, 0.0)
1379 newFunc.short_description = short_description
1380 newFunc.allow_tags = True
1383 class InvoiceChargeInline(PlStackTabularInline):
1386 verbose_name_plural = "Charges"
1387 verbose_name = "Charge"
1388 exclude = ['account']
1389 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1390 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1394 dollar_amount = right_dollar_field("amount", "Amount")
1396 class InvoiceAdmin(admin.ModelAdmin):
1397 list_display = ("date", "account")
1399 inlines = [InvoiceChargeInline]
1401 fields = ["date", "account", "dollar_amount"]
1402 readonly_fields = ["date", "account", "dollar_amount"]
1404 dollar_amount = dollar_field("amount", "Amount")
1406 class InvoiceInline(PlStackTabularInline):
1409 verbose_name_plural = "Invoices"
1410 verbose_name = "Invoice"
1411 fields = ["date", "dollar_amount"]
1412 readonly_fields = ["date", "dollar_amount"]
1413 suit_classes = 'suit-tab suit-tab-accountinvoice'
1417 dollar_amount = right_dollar_field("amount", "Amount")
1419 class PendingChargeInline(PlStackTabularInline):
1422 verbose_name_plural = "Charges"
1423 verbose_name = "Charge"
1424 exclude = ["invoice"]
1425 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1426 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1427 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1431 def queryset(self, request):
1432 qs = super(PendingChargeInline, self).queryset(request)
1433 qs = qs.filter(state="pending")
1436 dollar_amount = right_dollar_field("amount", "Amount")
1438 class PaymentInline(PlStackTabularInline):
1441 verbose_name_plural = "Payments"
1442 verbose_name = "Payment"
1443 fields = ["date", "dollar_amount"]
1444 readonly_fields = ["date", "dollar_amount"]
1445 suit_classes = 'suit-tab suit-tab-accountpayments'
1449 dollar_amount = right_dollar_field("amount", "Amount")
1451 class AccountAdmin(admin.ModelAdmin):
1452 list_display = ("site", "balance_due")
1454 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1457 (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1459 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1462 ('general','Account Details'),
1463 ('accountinvoice', 'Invoices'),
1464 ('accountpayments', 'Payments'),
1465 ('accountpendingcharges','Pending Charges'),
1468 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1469 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1470 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1472 # Now register the new UserAdmin...
1473 admin.site.register(User, UserAdmin)
1474 # ... and, since we're not using Django's builtin permissions,
1475 # unregister the Group model from admin.
1476 #admin.site.unregister(Group)
1478 #Do not show django evolution in the admin interface
1479 from django_evolution.models import Version, Evolution
1480 #admin.site.unregister(Version)
1481 #admin.site.unregister(Evolution)
1484 # When debugging it is often easier to see all the classes, but for regular use
1485 # only the top-levels should be displayed
1488 admin.site.register(Deployment, DeploymentAdmin)
1489 admin.site.register(Site, SiteAdmin)
1490 admin.site.register(Slice, SliceAdmin)
1491 admin.site.register(Service, ServiceAdmin)
1492 admin.site.register(Reservation, ReservationAdmin)
1493 admin.site.register(Network, NetworkAdmin)
1494 admin.site.register(Router, RouterAdmin)
1495 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1496 admin.site.register(Account, AccountAdmin)
1497 admin.site.register(Invoice, InvoiceAdmin)
1500 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1501 admin.site.register(ServiceClass, ServiceClassAdmin)
1502 #admin.site.register(PlanetStack)
1503 admin.site.register(Tag, TagAdmin)
1504 admin.site.register(DeploymentRole)
1505 admin.site.register(SiteRole)
1506 admin.site.register(SliceRole)
1507 admin.site.register(PlanetStackRole)
1508 admin.site.register(Node, NodeAdmin)
1509 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1510 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1511 admin.site.register(Sliver, SliverAdmin)
1512 admin.site.register(Image, ImageAdmin)
1513 admin.site.register(DashboardView, DashboardViewAdmin)
1514 admin.site.register(Flavor, FlavorAdmin)