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) )
766 for site in Site.objects.all():
\r
767 sites[site.id] = site.login_base
769 context["deployment_nodes"] = deployment_nodes
770 context["deployment_flavors"] = deployment_flavors
771 context["sites"] = sites
773 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
775 def formfield_for_foreignkey(self, db_field, request, **kwargs):
776 if db_field.name == 'site':
777 kwargs['queryset'] = Site.select_by_user(request.user)
778 kwargs['widget'] = forms.Select(attrs={'onChange': "update_slice_name(this, $($(this).closest('div')[0]).find('.field-name input')[0].id)"})
780 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
782 def queryset(self, request):
783 # admins can see all keys. Users can only see slices they belong to.
784 return Slice.select_by_user(request.user)
786 def get_formsets(self, request, obj=None):
787 for inline in self.get_inline_instances(request, obj):
788 # hide MyInline in the add view
791 if isinstance(inline, SliverInline):
792 inline.model.caller = request.user
793 yield inline.get_formset(request, obj)
796 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
798 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
800 readonly_fields = ('backend_status_text', )
801 list_display = ('backend_status_icon', 'user', 'slice', 'role')
802 list_display_links = list_display
804 user_readonly_fields = ['user', 'slice', 'role']
805 user_readonly_inlines = []
807 def formfield_for_foreignkey(self, db_field, request, **kwargs):
808 if db_field.name == 'slice':
809 kwargs['queryset'] = Slice.select_by_user(request.user)
811 if db_field.name == 'user':
812 kwargs['queryset'] = User.select_by_user(request.user)
814 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
816 def queryset(self, request):
817 # admins can see all memberships. Users can only see memberships of
818 # slices where they have the admin role.
819 return SlicePrivilege.select_by_user(request.user)
821 def save_model(self, request, obj, form, change):
822 # update openstack connection to use this site/tenant
823 auth = request.session.get('auth', {})
824 auth['tenant'] = obj.slice.slicename
825 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
828 def delete_model(self, request, obj):
829 # update openstack connection to use this site/tenant
830 auth = request.session.get('auth', {})
831 auth['tenant'] = obj.slice.slicename
832 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
836 class ImageAdmin(PlanetStackBaseAdmin):
838 fieldsets = [('Image Details',
839 {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
840 'classes': ['suit-tab suit-tab-general']})
842 readonly_fields = ('backend_status_text', )
844 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
846 inlines = [SliverInline, ImageDeploymentsInline]
848 user_readonly_fields = ['name', 'disk_format', 'container_format']
850 list_display = ['backend_status_icon', 'name']
851 list_display_links = ('backend_status_icon', 'name', )
853 class NodeForm(forms.ModelForm):
856 'site': LinkedSelect,
857 'deployment': LinkedSelect
860 class NodeAdmin(PlanetStackBaseAdmin):
862 list_display = ('backend_status_icon', 'name', 'site', 'deployment')
863 list_display_links = ('backend_status_icon', 'name', )
864 list_filter = ('deployment',)
866 inlines = [TagInline,SliverInline]
867 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
868 readonly_fields = ('backend_status_text', )
870 user_readonly_fields = ['name','site','deployment']
871 user_readonly_inlines = [TagInline,SliverInline]
873 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
876 class SliverForm(forms.ModelForm):
879 ip = forms.CharField(widget=PlainTextWidget)
880 instance_name = forms.CharField(widget=PlainTextWidget)
882 'ip': PlainTextWidget(),
883 'instance_name': PlainTextWidget(),
884 'slice': LinkedSelect,
885 'deploymentNetwork': LinkedSelect,
886 'node': LinkedSelect,
887 'image': LinkedSelect
890 class TagAdmin(PlanetStackBaseAdmin):
891 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
892 list_display_links = list_display
893 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
894 user_readonly_inlines = []
896 class SliverAdmin(PlanetStackBaseAdmin):
899 ('Sliver Details', {'fields': ['backend_status_text', 'slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
901 readonly_fields = ('backend_status_text', )
902 list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'flavor', 'image', 'node', 'deploymentNetwork']
903 list_display_links = ('backend_status_icon', 'ip',)
905 suit_form_tabs =(('general', 'Sliver Details'),
909 inlines = [TagInline]
911 user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image']
913 def formfield_for_foreignkey(self, db_field, request, **kwargs):
914 if db_field.name == 'slice':
915 kwargs['queryset'] = Slice.select_by_user(request.user)
917 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
919 def queryset(self, request):
920 # admins can see all slivers. Users can only see slivers of
921 # the slices they belong to.
922 return Sliver.select_by_user(request.user)
925 def get_formsets(self, request, obj=None):
926 # make some fields read only if we are updating an existing record
928 #self.readonly_fields = ('ip', 'instance_name')
929 self.readonly_fields = ('backend_status_text')
931 self.readonly_fields = ('backend_status_text')
932 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
934 for inline in self.get_inline_instances(request, obj):
935 # hide MyInline in the add view
938 if isinstance(inline, SliverInline):
939 inline.model.caller = request.user
940 yield inline.get_formset(request, obj)
942 #def save_model(self, request, obj, form, change):
943 # # update openstack connection to use this site/tenant
944 # auth = request.session.get('auth', {})
945 # auth['tenant'] = obj.slice.name
946 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
947 # obj.creator = request.user
950 #def delete_model(self, request, obj):
951 # # update openstack connection to use this site/tenant
952 # auth = request.session.get('auth', {})
953 # auth['tenant'] = obj.slice.name
954 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
957 class UserCreationForm(forms.ModelForm):
958 """A form for creating new users. Includes all the required
959 fields, plus a repeated password."""
960 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
961 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
965 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
967 def clean_password2(self):
968 # Check that the two password entries match
969 password1 = self.cleaned_data.get("password1")
970 password2 = self.cleaned_data.get("password2")
971 if password1 and password2 and password1 != password2:
972 raise forms.ValidationError("Passwords don't match")
975 def save(self, commit=True):
976 # Save the provided password in hashed format
977 user = super(UserCreationForm, self).save(commit=False)
978 user.password = self.cleaned_data["password1"]
979 #user.set_password(self.cleaned_data["password1"])
985 class UserChangeForm(forms.ModelForm):
986 """A form for updating users. Includes all the fields on
987 the user, but replaces the password field with admin's
988 password hash display field.
990 password = ReadOnlyPasswordHashField(label='Password',
991 help_text= '<a href=\"password/\">Change Password</a>.')
996 def clean_password(self):
997 # Regardless of what the user provides, return the initial value.
998 # This is done here, rather than on the field, because the
999 # field does not have access to the initial value
1000 return self.initial["password"]
1002 class UserDashboardViewInline(PlStackTabularInline):
1003 model = UserDashboardView
1005 suit_classes = 'suit-tab suit-tab-dashboards'
1006 fields = ['user', 'dashboardView', 'order']
1008 class UserAdmin(UserAdmin):
1012 # The forms to add and change user instances
1013 form = UserChangeForm
1014 add_form = UserCreationForm
1016 # The fields to be used in displaying the User model.
1017 # These override the definitions on the base UserAdmin
1018 # that reference specific fields on auth.User.
1019 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
1020 list_filter = ('site',)
1021 inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
1023 fieldListLoginDetails = ['email','site','password','is_active','is_readonly','is_admin','public_key']
1024 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1027 ('Login Details', {'fields': ['backend_status_text', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1028 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1029 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1030 #('Important dates', {'fields': ('last_login',)}),
1034 'classes': ('wide',),
1035 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
1038 readonly_fields = ('backend_status_text', )
1039 search_fields = ('email',)
1040 ordering = ('email',)
1041 filter_horizontal = ()
1043 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1045 suit_form_tabs =(('general','Login Details'),
1046 ('contact','Contact Information'),
1047 ('sliceprivileges','Slice Privileges'),
1048 ('siteprivileges','Site Privileges'),
1049 ('deploymentprivileges','Deployment Privileges'),
1050 ('dashboards','Dashboard Views'))
1052 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1053 if db_field.name == 'site':
1054 kwargs['queryset'] = Site.select_by_user(request.user)
1056 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1058 def has_add_permission(self, request, obj=None):
1059 return (not self.__user_is_readonly(request))
1061 def has_delete_permission(self, request, obj=None):
1062 return (not self.__user_is_readonly(request))
1064 def get_actions(self,request):
1065 actions = super(UserAdmin,self).get_actions(request)
1067 if self.__user_is_readonly(request):
1068 if 'delete_selected' in actions:
1069 del actions['delete_selected']
1073 def change_view(self,request,object_id, extra_context=None):
1075 if self.__user_is_readonly(request):
1076 if not hasattr(self, "readonly_save"):
1077 # save the original readonly fields
\r
1078 self.readonly_save = self.readonly_fields
\r
1079 self.inlines_save = self.inlines
1080 if hasattr(self, "user_readonly_fields"):
1081 self.readonly_fields=self.user_readonly_fields
1082 if hasattr(self, "user_readonly_inlines"):
1083 self.inlines = self.user_readonly_inlines
1085 if hasattr(self, "readonly_save"):
\r
1086 # restore the original readonly fields
\r
1087 self.readonly_fields = self.readonly_save
\r
1088 self.inlines = self.inlines_save
1091 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1092 except PermissionDenied:
1094 if request.method == 'POST':
1095 raise PermissionDenied
1096 request.readonly = True
1097 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1099 def __user_is_readonly(self, request):
1100 #groups = [x.name for x in request.user.groups.all() ]
1101 #return "readonly" in groups
1102 return request.user.isReadOnlyUser()
1104 def queryset(self, request):
1105 return User.select_by_user(request.user)
1107 def backend_status_text(self, obj):
1108 return mark_safe(backend_text(obj))
1110 def backend_status_icon(self, obj):
1111 return mark_safe(backend_icon(obj))
1112 backend_status_icon.short_description = ""
1114 class DashboardViewAdmin(PlanetStackBaseAdmin):
1115 fieldsets = [('Dashboard View Details',
1116 {'fields': ['backend_status_text', 'name', 'url'],
1117 'classes': ['suit-tab suit-tab-general']})
1119 readonly_fields = ('backend_status_text', )
1121 suit_form_tabs =(('general','Dashboard View Details'),)
1123 class ServiceResourceInline(PlStackTabularInline):
1124 model = ServiceResource
1127 class ServiceClassAdmin(PlanetStackBaseAdmin):
1128 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1129 list_display_links = ('backend_status_icon', 'name', )
1130 inlines = [ServiceResourceInline]
1132 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1133 user_readonly_inlines = []
1135 class ReservedResourceInline(PlStackTabularInline):
1136 model = ReservedResource
1138 suit_classes = 'suit-tab suit-tab-reservedresources'
1140 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1141 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1143 if db_field.name == 'resource':
1144 # restrict resources to those that the slice's service class allows
1145 if request._slice is not None:
1146 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1147 if len(field.queryset) > 0:
1148 field.initial = field.queryset.all()[0]
1150 field.queryset = field.queryset.none()
\r
1151 elif db_field.name == 'sliver':
\r
1152 # restrict slivers to those that belong to the slice
\r
1153 if request._slice is not None:
\r
1154 field.queryset = field.queryset.filter(slice = request._slice)
1156 field.queryset = field.queryset.none()
\r
1160 def queryset(self, request):
1161 return ReservedResource.select_by_user(request.user)
1163 class ReservationChangeForm(forms.ModelForm):
1167 'slice' : LinkedSelect
1170 class ReservationAddForm(forms.ModelForm):
1171 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1172 refresh = forms.CharField(widget=forms.HiddenInput())
1175 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1177 def clean_slice(self):
1178 slice = self.cleaned_data.get("slice")
1179 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1181 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1187 'slice' : LinkedSelect
1191 class ReservationAddRefreshForm(ReservationAddForm):
1192 """ This form is displayed when the Reservation Form receives an update
1193 from the Slice dropdown onChange handler. It doesn't validate the
1194 data and doesn't save the data. This will cause the form to be
1198 """ don't validate anything other than slice """
1199 dont_validate_fields = ("startTime", "duration")
1201 def full_clean(self):
1202 result = super(ReservationAddForm, self).full_clean()
1204 for fieldname in self.dont_validate_fields:
1205 if fieldname in self._errors:
1206 del self._errors[fieldname]
1210 """ don't save anything """
1214 class ReservationAdmin(PlanetStackBaseAdmin):
1215 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
1216 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1217 readonly_fields = ('backend_status_text', )
1218 list_display = ('startTime', 'duration')
1219 form = ReservationAddForm
1221 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1223 inlines = [ReservedResourceInline]
1224 user_readonly_fields = fieldList
1226 def add_view(self, request, form_url='', extra_context=None):
1227 timezone.activate(request.user.timezone)
1228 request._refresh = False
1229 request._slice = None
1230 if request.method == 'POST':
1231 # "refresh" will be set to "1" if the form was submitted due to
1232 # a change in the Slice dropdown.
1233 if request.POST.get("refresh","1") == "1":
1234 request._refresh = True
1235 request.POST["refresh"] = "0"
1237 # Keep track of the slice that was selected, so the
1238 # reservedResource inline can filter items for the slice.
1239 request._slice = request.POST.get("slice",None)
1240 if (request._slice is not None):
1241 request._slice = Slice.objects.get(id=request._slice)
1243 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1246 def changelist_view(self, request, extra_context = None):
1247 timezone.activate(request.user.timezone)
1248 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1250 def get_form(self, request, obj=None, **kwargs):
1253 # For changes, set request._slice to the slice already set in the
1255 request._slice = obj.slice
1256 self.form = ReservationChangeForm
1258 if getattr(request, "_refresh", False):
1259 self.form = ReservationAddRefreshForm
1261 self.form = ReservationAddForm
1262 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1264 def get_readonly_fields(self, request, obj=None):
1265 if (obj is not None):
1266 # Prevent slice from being changed after the reservation has been
1272 def queryset(self, request):
1273 return Reservation.select_by_user(request.user)
1275 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1276 list_display = ("backend_status_icon", "name", )
1277 list_display_links = ('backend_status_icon', 'name', )
1278 user_readonly_fields = ['name']
1279 user_readonly_inlines = []
1281 class RouterAdmin(PlanetStackBaseAdmin):
1282 list_display = ("backend_status_icon", "name", )
1283 list_display_links = ('backend_status_icon', 'name', )
1284 user_readonly_fields = ['name']
1285 user_readonly_inlines = []
1287 class RouterInline(PlStackTabularInline):
1288 model = Router.networks.through
1290 verbose_name_plural = "Routers"
1291 verbose_name = "Router"
1292 suit_classes = 'suit-tab suit-tab-routers'
1294 class NetworkParameterInline(PlStackGenericTabularInline):
1295 model = NetworkParameter
1297 verbose_name_plural = "Parameters"
1298 verbose_name = "Parameter"
1299 suit_classes = 'suit-tab suit-tab-netparams'
1300 fields = ['backend_status_icon', 'parameter', 'value']
1301 readonly_fields = ('backend_status_icon', )
1303 class NetworkSliversInline(PlStackTabularInline):
1304 fields = ['backend_status_icon', 'network','sliver','ip']
1305 readonly_fields = ("backend_status_icon", "ip", )
1306 model = NetworkSliver
1307 selflink_fieldname = "sliver"
1309 verbose_name_plural = "Slivers"
1310 verbose_name = "Sliver"
1311 suit_classes = 'suit-tab suit-tab-networkslivers'
1313 class NetworkSlicesInline(PlStackTabularInline):
1314 model = NetworkSlice
1315 selflink_fieldname = "slice"
1317 verbose_name_plural = "Slices"
1318 verbose_name = "Slice"
1319 suit_classes = 'suit-tab suit-tab-networkslices'
1320 fields = ['backend_status_icon', 'network','slice']
1321 readonly_fields = ('backend_status_icon', )
1323 class NetworkAdmin(PlanetStackBaseAdmin):
1324 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1325 list_display_links = ('backend_status_icon', 'name', )
1326 readonly_fields = ("subnet", )
1328 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1331 (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']}),]
1333 readonly_fields = ('backend_status_text', )
1334 user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1337 ('general','Network Details'),
1338 ('netparams', 'Parameters'),
1339 ('networkslivers','Slivers'),
1340 ('networkslices','Slices'),
1341 ('routers','Routers'),
1343 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1344 list_display = ("backend_status_icon", "name", "guaranteedBandwidth", "visibility")
1345 list_display_links = ('backend_status_icon', 'name', )
1346 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1347 user_readonly_inlines = []
1349 class FlavorAdmin(PlanetStackBaseAdmin):
1350 list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1351 list_display_links = ("backend_status_icon", "name")
1352 user_readonly_fields = ("name", "flavor")
1353 fields = ("name", "description", "flavor", "order", "default")
1355 # register a signal that caches the user's credentials when they log in
1356 def cache_credentials(sender, user, request, **kwds):
1357 auth = {'username': request.POST['username'],
1358 'password': request.POST['password']}
1359 request.session['auth'] = auth
1360 user_logged_in.connect(cache_credentials)
1362 def dollar_field(fieldName, short_description):
1363 def newFunc(self, obj):
1365 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1367 x=getattr(obj, fieldName, 0.0)
1369 newFunc.short_description = short_description
1372 def right_dollar_field(fieldName, short_description):
1373 def newFunc(self, obj):
1375 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1376 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1378 x=getattr(obj, fieldName, 0.0)
1380 newFunc.short_description = short_description
1381 newFunc.allow_tags = True
1384 class InvoiceChargeInline(PlStackTabularInline):
1387 verbose_name_plural = "Charges"
1388 verbose_name = "Charge"
1389 exclude = ['account']
1390 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1391 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1395 dollar_amount = right_dollar_field("amount", "Amount")
1397 class InvoiceAdmin(admin.ModelAdmin):
1398 list_display = ("date", "account")
1400 inlines = [InvoiceChargeInline]
1402 fields = ["date", "account", "dollar_amount"]
1403 readonly_fields = ["date", "account", "dollar_amount"]
1405 dollar_amount = dollar_field("amount", "Amount")
1407 class InvoiceInline(PlStackTabularInline):
1410 verbose_name_plural = "Invoices"
1411 verbose_name = "Invoice"
1412 fields = ["date", "dollar_amount"]
1413 readonly_fields = ["date", "dollar_amount"]
1414 suit_classes = 'suit-tab suit-tab-accountinvoice'
1418 dollar_amount = right_dollar_field("amount", "Amount")
1420 class PendingChargeInline(PlStackTabularInline):
1423 verbose_name_plural = "Charges"
1424 verbose_name = "Charge"
1425 exclude = ["invoice"]
1426 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1427 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1428 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1432 def queryset(self, request):
1433 qs = super(PendingChargeInline, self).queryset(request)
1434 qs = qs.filter(state="pending")
1437 dollar_amount = right_dollar_field("amount", "Amount")
1439 class PaymentInline(PlStackTabularInline):
1442 verbose_name_plural = "Payments"
1443 verbose_name = "Payment"
1444 fields = ["date", "dollar_amount"]
1445 readonly_fields = ["date", "dollar_amount"]
1446 suit_classes = 'suit-tab suit-tab-accountpayments'
1450 dollar_amount = right_dollar_field("amount", "Amount")
1452 class AccountAdmin(admin.ModelAdmin):
1453 list_display = ("site", "balance_due")
1455 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1458 (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1460 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1463 ('general','Account Details'),
1464 ('accountinvoice', 'Invoices'),
1465 ('accountpayments', 'Payments'),
1466 ('accountpendingcharges','Pending Charges'),
1469 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1470 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1471 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1473 # Now register the new UserAdmin...
1474 admin.site.register(User, UserAdmin)
1475 # ... and, since we're not using Django's builtin permissions,
1476 # unregister the Group model from admin.
1477 #admin.site.unregister(Group)
1479 #Do not show django evolution in the admin interface
1480 from django_evolution.models import Version, Evolution
1481 #admin.site.unregister(Version)
1482 #admin.site.unregister(Evolution)
1485 # When debugging it is often easier to see all the classes, but for regular use
1486 # only the top-levels should be displayed
1489 admin.site.register(Deployment, DeploymentAdmin)
1490 admin.site.register(Site, SiteAdmin)
1491 admin.site.register(Slice, SliceAdmin)
1492 admin.site.register(Service, ServiceAdmin)
1493 admin.site.register(Reservation, ReservationAdmin)
1494 admin.site.register(Network, NetworkAdmin)
1495 admin.site.register(Router, RouterAdmin)
1496 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1497 admin.site.register(Account, AccountAdmin)
1498 admin.site.register(Invoice, InvoiceAdmin)
1501 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1502 admin.site.register(ServiceClass, ServiceClassAdmin)
1503 #admin.site.register(PlanetStack)
1504 admin.site.register(Tag, TagAdmin)
1505 admin.site.register(DeploymentRole)
1506 admin.site.register(SiteRole)
1507 admin.site.register(SliceRole)
1508 admin.site.register(PlanetStackRole)
1509 admin.site.register(Node, NodeAdmin)
1510 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1511 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1512 admin.site.register(Sliver, SliverAdmin)
1513 admin.site.register(Image, ImageAdmin)
1514 admin.site.register(DashboardView, DashboardViewAdmin)
1515 admin.site.register(Flavor, FlavorAdmin)