1 from core.models import Site
2 from core.models import *
3 from openstack.manager import OpenStackManager
5 from django.contrib import admin
6 from django.contrib.auth.models import Group
7 from django import forms
8 from django.utils.safestring import mark_safe
9 from django.contrib.auth.admin import UserAdmin
10 from django.contrib.admin.widgets import FilteredSelectMultiple
11 from django.contrib.auth.forms import ReadOnlyPasswordHashField
12 from django.contrib.auth.signals import user_logged_in
13 from django.utils import timezone
14 from django.contrib.contenttypes import generic
15 from suit.widgets import LinkedSelect
16 from django.core.exceptions import PermissionDenied
17 from django.core.urlresolvers import reverse, NoReverseMatch
19 import django_evolution
21 def backend_icon(obj): # backend_status, enacted, updated):
22 #return "%s %s %s" % (str(obj.updated), str(obj.enacted), str(obj.backend_status))
23 if (obj.enacted is not None) and obj.enacted >= obj.updated:
24 return '<span style="min-width:16px;"><img src="/static/admin/img/icon_success.gif"></span>'
26 if obj.backend_status == "Provisioning in progress" or obj.backend_status=="":
27 return '<span style="min-width:16px;" title="%s"><img src="/static/admin/img/icon_clock.gif"></span>' % obj.backend_status
29 return '<span style="min-width:16px;" title="%s"><img src="/static/admin/img/icon_error.gif"></span>' % obj.backend_status
31 def backend_text(obj):
32 icon = backend_icon(obj)
33 if (obj.enacted is not None) and obj.enacted >= obj.updated:
34 return "%s %s" % (icon, "successfully enacted") # enacted on %s" % str(obj.enacted))
36 return "%s %s" % (icon, obj.backend_status)
38 class PlainTextWidget(forms.HiddenInput):
41 def render(self, name, value, attrs=None):
44 return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
46 class ReadOnlyAwareAdmin(admin.ModelAdmin):
48 def has_add_permission(self, request, obj=None):
49 return (not self.__user_is_readonly(request))
51 def has_delete_permission(self, request, obj=None):
52 return (not self.__user_is_readonly(request))
54 def save_model(self, request, obj, form, change):
55 if self.__user_is_readonly(request):
56 raise PermissionDenied
59 return super(ReadOnlyAwareAdmin, self).save_model(request, obj, form, change)
61 def get_actions(self,request):
62 actions = super(ReadOnlyAwareAdmin,self).get_actions(request)
64 if self.__user_is_readonly(request):
65 if 'delete_selected' in actions:
66 del actions['delete_selected']
70 def change_view(self,request,object_id, extra_context=None):
71 if self.__user_is_readonly(request):
72 if not hasattr(self, "readonly_save"):
\r
73 # save the original readonly fields
\r
74 self.readonly_save = self.readonly_fields
\r
75 self.inlines_save = self.inlines
\r
76 if hasattr(self, "user_readonly_fields"):
\r
77 self.readonly_fields=self.user_readonly_fields
\r
78 if hasattr(self, "user_readonly_inlines"):
\r
79 self.inlines = self.user_readonly_inlines
\r
81 if hasattr(self, "readonly_save"):
\r
82 # restore the original readonly fields
\r
83 self.readonly_fields = self.readonly_save
\r
84 if hasattr(self, "inlines_save"):
\r
85 self.inlines = self.inlines_save
88 return super(ReadOnlyAwareAdmin, self).change_view(request, object_id, extra_context=extra_context)
89 except PermissionDenied:
91 if request.method == 'POST':
92 raise PermissionDenied
93 request.readonly = True
94 return super(ReadOnlyAwareAdmin, self).change_view(request, object_id, extra_context=extra_context)
96 def __user_is_readonly(self, request):
97 return request.user.isReadOnlyUser()
99 def backend_status_text(self, obj):
100 return mark_safe(backend_text(obj))
102 def backend_status_icon(self, obj):
103 return mark_safe(backend_icon(obj))
104 backend_status_icon.short_description = ""
107 class SingletonAdmin (ReadOnlyAwareAdmin):
108 def has_add_permission(self, request):
109 if not super(SingletonAdmin, self).has_add_permission(request):
112 num_objects = self.model.objects.count()
119 class PlStackTabularInline(admin.TabularInline):
120 def __init__(self, *args, **kwargs):
121 super(PlStackTabularInline, self).__init__(*args, **kwargs)
123 # InlineModelAdmin as no get_fields() method, so in order to add
124 # the selflink field, we override __init__ to modify self.fields and
125 # self.readonly_fields.
127 self.setup_selflink()
129 def get_change_url(self, model, id):
130 """ Get the URL to a change form in the admin for this model """
131 reverse_path = "admin:%s_change" % (model._meta.db_table)
133 url = reverse(reverse_path, args=(id,))
134 except NoReverseMatch:
139 def setup_selflink(self):
140 if hasattr(self, "selflink_fieldname"):
141 """ self.selflink_model can be defined to punch through a relation
142 to its target object. For example, in SliceNetworkInline, set
143 selflink_model = "network", and the URL will lead to the Network
144 object instead of trying to bring up a change view of the
147 self.selflink_model = getattr(self.model,self.selflink_fieldname).field.rel.to
149 self.selflink_model = self.model
151 url = self.get_change_url(self.selflink_model, 0)
153 # We don't have an admin for this object, so don't create the
158 # Since we need to add "selflink" to the field list, we need to create
159 # self.fields if it is None.
160 if (self.fields is None):
162 for f in self.model._meta.fields:
163 if f.editable and f.name != "id":
164 self.fields.append(f.name)
166 self.fields = tuple(self.fields) + ("selflink", )
168 if self.readonly_fields is None:
169 self.readonly_fields = ()
171 self.readonly_fields = tuple(self.readonly_fields) + ("selflink", )
173 def selflink(self, obj):
174 if hasattr(self, "selflink_fieldname"):
175 obj = getattr(obj, self.selflink_fieldname)
178 url = self.get_change_url(self.selflink_model, obj.id)
179 return "<a href='%s'>Details</a>" % str(url)
181 return "Not present"
\r
183 selflink.allow_tags = True
184 selflink.short_description = "Details"
186 def has_add_permission(self, request):
187 return not request.user.isReadOnlyUser()
189 def get_readonly_fields(self, request, obj=None):
190 readonly_fields = list(self.readonly_fields)[:]
191 if request.user.isReadOnlyUser():
192 for field in self.fields:
193 if not field in readonly_fields:
194 readonly_fields.append(field)
195 return readonly_fields
197 def backend_status_icon(self, obj):
198 return mark_safe(backend_icon(obj))
199 backend_status_icon.short_description = ""
201 class PlStackGenericTabularInline(generic.GenericTabularInline):
202 def has_add_permission(self, request):
203 return not request.user.isReadOnlyUser()
205 def get_readonly_fields(self, request, obj=None):
206 readonly_fields = list(self.readonly_fields)[:]
207 if request.user.isReadOnlyUser():
208 for field in self.fields:
209 if not field in readonly_fields:
210 readonly_fields.append(field)
211 return readonly_fields
213 def backend_status_icon(self, obj):
214 return mark_safe(backend_icon(obj))
215 backend_status_icon.short_description = ""
217 class ReservationInline(PlStackTabularInline):
220 suit_classes = 'suit-tab suit-tab-reservations'
222 def queryset(self, request):
223 return Reservation.select_by_user(request.user)
225 class TagInline(PlStackGenericTabularInline):
228 suit_classes = 'suit-tab suit-tab-tags'
229 fields = ['service', 'name', 'value']
231 def queryset(self, request):
232 return Tag.select_by_user(request.user)
234 class NetworkLookerUpper:
235 """ This is a callable that looks up a network name in a sliver and returns
236 the ip address for that network.
239 byNetworkName = {} # class variable
241 def __init__(self, name):
242 self.short_description = name
244 self.network_name = name
246 def __call__(self, obj):
248 for nbs in obj.networksliver_set.all():
249 if (nbs.network.name == self.network_name):
254 return self.network_name
257 def get(network_name):
258 """ We want to make sure we alwars return the same NetworkLookerUpper
259 because sometimes django will cause them to be instantiated multiple
260 times (and we don't want different ones in form.fields vs
261 SliverInline.readonly_fields).
263 if network_name not in NetworkLookerUpper.byNetworkName:
264 NetworkLookerUpper.byNetworkName[network_name] = NetworkLookerUpper(network_name)
265 return NetworkLookerUpper.byNetworkName[network_name]
267 class SliverInline(PlStackTabularInline):
269 fields = ['backend_status_icon', 'all_ips_string', 'instance_name', 'slice', 'deploymentNetwork', 'flavor', 'image', 'node']
271 readonly_fields = ['backend_status_icon', 'all_ips_string', 'instance_name']
272 suit_classes = 'suit-tab suit-tab-slivers'
274 def queryset(self, request):
275 return Sliver.select_by_user(request.user)
277 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
278 if db_field.name == 'deploymentNetwork':
279 kwargs['queryset'] = Deployment.select_by_acl(request.user)
280 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_deployment_changed(this);"})
281 elif db_field.name == 'flavor':
282 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_flavor_changed(this);"})
284 field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
288 class SiteInline(PlStackTabularInline):
291 suit_classes = 'suit-tab suit-tab-sites'
293 def queryset(self, request):
294 return Site.select_by_user(request.user)
296 class UserInline(PlStackTabularInline):
298 fields = ['backend_status_icon', 'email', 'firstname', 'lastname']
299 readonly_fields = ('backend_status_icon', )
301 suit_classes = 'suit-tab suit-tab-users'
303 def queryset(self, request):
304 return User.select_by_user(request.user)
306 class SliceInline(PlStackTabularInline):
308 fields = ['backend_status_icon', 'name', 'site', 'serviceClass', 'service']
309 readonly_fields = ('backend_status_icon', )
311 suit_classes = 'suit-tab suit-tab-slices'
313 def queryset(self, request):
314 return Slice.select_by_user(request.user)
316 class NodeInline(PlStackTabularInline):
319 suit_classes = 'suit-tab suit-tab-nodes'
320 fields = ['backend_status_icon', 'name','deployment','site']
321 readonly_fields = ('backend_status_icon', )
323 class DeploymentPrivilegeInline(PlStackTabularInline):
324 model = DeploymentPrivilege
326 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
327 fields = ['backend_status_icon', 'user','role','deployment']
328 readonly_fields = ('backend_status_icon', )
330 def queryset(self, request):
331 return DeploymentPrivilege.select_by_user(request.user)
333 class SitePrivilegeInline(PlStackTabularInline):
334 model = SitePrivilege
336 suit_classes = 'suit-tab suit-tab-siteprivileges'
337 fields = ['backend_status_icon', 'user','site', 'role']
338 readonly_fields = ('backend_status_icon', )
340 def formfield_for_foreignkey(self, db_field, request, **kwargs):
341 if db_field.name == 'site':
342 kwargs['queryset'] = Site.select_by_user(request.user)
344 if db_field.name == 'user':
345 kwargs['queryset'] = User.select_by_user(request.user)
346 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
348 def queryset(self, request):
349 return SitePrivilege.select_by_user(request.user)
351 class SiteDeploymentInline(PlStackTabularInline):
352 model = SiteDeployments
354 suit_classes = 'suit-tab suit-tab-deployments'
355 fields = ['backend_status_icon', 'deployment','site']
356 readonly_fields = ('backend_status_icon', )
358 def formfield_for_foreignkey(self, db_field, request, **kwargs):
359 if db_field.name == 'site':
360 kwargs['queryset'] = Site.select_by_user(request.user)
362 if db_field.name == 'deployment':
363 kwargs['queryset'] = Deployment.select_by_user(request.user)
364 return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
366 def queryset(self, request):
367 return SiteDeployments.select_by_user(request.user)
370 class SlicePrivilegeInline(PlStackTabularInline):
371 model = SlicePrivilege
372 suit_classes = 'suit-tab suit-tab-sliceprivileges'
374 fields = ('backend_status_icon', 'user', 'slice', 'role')
375 readonly_fields = ('backend_status_icon', )
377 def formfield_for_foreignkey(self, db_field, request, **kwargs):
378 if db_field.name == 'slice':
379 kwargs['queryset'] = Slice.select_by_user(request.user)
380 if db_field.name == 'user':
381 kwargs['queryset'] = User.select_by_user(request.user)
383 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
385 def queryset(self, request):
386 return SlicePrivilege.select_by_user(request.user)
388 class SliceNetworkInline(PlStackTabularInline):
389 model = Network.slices.through
390 selflink_fieldname = "network"
392 verbose_name = "Network Connection"
393 verbose_name_plural = "Network Connections"
394 suit_classes = 'suit-tab suit-tab-slicenetworks'
395 fields = ['backend_status_icon', 'network']
396 readonly_fields = ('backend_status_icon', )
398 class ImageDeploymentsInline(PlStackTabularInline):
399 model = ImageDeployments
401 verbose_name = "Image Deployments"
402 verbose_name_plural = "Image Deployments"
403 suit_classes = 'suit-tab suit-tab-imagedeployments'
404 fields = ['backend_status_icon', 'image', 'deployment', 'glance_image_id']
405 readonly_fields = ['backend_status_icon', 'glance_image_id']
407 class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
410 def save_model(self, request, obj, form, change):
411 obj.caller = request.user
412 # update openstack connection to use this site/tenant
413 obj.save_by_user(request.user)
415 def delete_model(self, request, obj):
416 obj.delete_by_user(request.user)
418 def save_formset(self, request, form, formset, change):
419 instances = formset.save(commit=False)
420 for instance in instances:
421 instance.save_by_user(request.user)
423 # BUG in django 1.7? Objects are not deleted by formset.save if
424 # commit is False. So let's delete them ourselves.
426 # code from forms/models.py save_existing_objects()
428 forms_to_delete = formset.deleted_forms
\r
429 except AttributeError:
\r
431 if formset.initial_forms:
432 for form in formset.initial_forms:
434 if form in forms_to_delete:
437 formset.deleted_objects.append(obj)
442 class SliceRoleAdmin(PlanetStackBaseAdmin):
446 class SiteRoleAdmin(PlanetStackBaseAdmin):
450 class DeploymentAdminForm(forms.ModelForm):
451 sites = forms.ModelMultipleChoiceField(
452 queryset=Site.objects.all(),
454 help_text="Select which sites are allowed to host nodes in this deployment",
455 widget=FilteredSelectMultiple(
456 verbose_name=('Sites'), is_stacked=False
459 images = forms.ModelMultipleChoiceField(
460 queryset=Image.objects.all(),
462 help_text="Select which images should be deployed on this deployment",
463 widget=FilteredSelectMultiple(
464 verbose_name=('Images'), is_stacked=False
467 flavors = forms.ModelMultipleChoiceField(
468 queryset=Flavor.objects.all(),
470 help_text="Select which flavors should be usable on this deployment",
471 widget=FilteredSelectMultiple(
472 verbose_name=('Flavors'), is_stacked=False
477 many_to_many = ["flavors",]
479 def __init__(self, *args, **kwargs):
480 request = kwargs.pop('request', None)
481 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
483 self.fields['accessControl'].initial = "allow site " + request.user.site.name
485 if self.instance and self.instance.pk:
486 self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments_set.all()]
487 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments_set.all()]
488 self.fields['flavors'].initial = self.instance.flavors.all()
490 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
491 """ helper function for handling m2m relations from the MultipleChoiceField
493 this_obj: the source object we want to link from
495 selected_objs: a list of destination objects we want to link to
497 all_relations: the full set of relations involving this_obj, including ones we don't want
499 relation_class: the class that implements the relation from source to dest
501 local_attrname: field name representing this_obj in relation_class
503 foreign_attrname: field name representing selected_objs in relation_class
505 This function will remove all newobjclass relations from this_obj
506 that are not contained in selected_objs, and add any relations that
507 are in selected_objs but don't exist in the data model yet.
510 existing_dest_objs = []
511 for relation in list(all_relations):
512 if getattr(relation, foreign_attrname) not in selected_objs:
513 #print "deleting site", sdp.site
516 existing_dest_objs.append(getattr(relation, foreign_attrname))
518 for dest_obj in selected_objs:
519 if dest_obj not in existing_dest_objs:
520 #print "adding site", site
521 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
522 relation = relation_class(**kwargs)
525 def save(self, commit=True):
526 deployment = super(DeploymentAdminForm, self).save(commit=False)
528 deployment.flavors = self.cleaned_data['flavors']
534 # save_m2m() doesn't seem to work with 'through' relations. So we
535 # create/destroy the through models ourselves. There has to be
538 self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments_set.all(), SiteDeployments, "deployment", "site")
539 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments_set.all(), ImageDeployments, "deployment", "image")
545 class DeploymentAdminROForm(DeploymentAdminForm):
546 def save(self, commit=True):
547 raise PermissionDenied
549 class SiteAssocInline(PlStackTabularInline):
550 model = Site.deployments.through
552 suit_classes = 'suit-tab suit-tab-sites'
554 class DeploymentAdmin(PlanetStackBaseAdmin):
556 fieldList = ['backend_status_text', 'name', 'availability_zone', 'sites', 'images', 'flavors', 'accessControl']
557 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
558 inlines = [DeploymentPrivilegeInline,NodeInline,TagInline] # ,ImageDeploymentsInline]
559 list_display = ['backend_status_icon', 'name']
560 list_display_links = ('backend_status_icon', 'name', )
561 readonly_fields = ('backend_status_text', )
563 user_readonly_fields = ['name']
565 suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags')) # ,('imagedeployments','Images'))
567 def get_form(self, request, obj=None, **kwargs):
568 if request.user.isReadOnlyUser():
569 kwargs["form"] = DeploymentAdminROForm
571 kwargs["form"] = DeploymentAdminForm
572 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
574 # from stackexchange: pass the request object into the form
576 class AdminFormMetaClass(adminForm):
577 def __new__(cls, *args, **kwargs):
578 kwargs['request'] = request
579 return adminForm(*args, **kwargs)
581 return AdminFormMetaClass
583 class ServiceAttrAsTabInline(PlStackTabularInline):
584 model = ServiceAttribute
585 fields = ['name','value']
587 suit_classes = 'suit-tab suit-tab-serviceattrs'
589 class ServiceAdmin(PlanetStackBaseAdmin):
590 list_display = ("backend_status_icon","name","description","versionNumber","enabled","published")
591 list_display_links = ('backend_status_icon', 'name', )
592 fieldList = ["backend_status_text","name","description","versionNumber","enabled","published"]
593 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
594 inlines = [ServiceAttrAsTabInline,SliceInline]
595 readonly_fields = ('backend_status_text', )
597 user_readonly_fields = fieldList
599 suit_form_tabs =(('general', 'Service Details'),
601 ('serviceattrs','Additional Attributes'),
604 class SiteAdmin(PlanetStackBaseAdmin):
605 fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
607 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
608 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
610 suit_form_tabs =(('general', 'Site Details'),
612 ('siteprivileges','Privileges'),
613 ('deployments','Deployments'),
618 readonly_fields = ['backend_status_text', 'accountLink']
620 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
622 list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
623 list_display_links = ('backend_status_icon', 'name', )
624 filter_horizontal = ('deployments',)
625 inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentInline]
626 search_fields = ['name']
628 def queryset(self, request):
629 return Site.select_by_user(request.user)
631 def get_formsets(self, request, obj=None):
632 for inline in self.get_inline_instances(request, obj):
633 # hide MyInline in the add view
636 if isinstance(inline, SliceInline):
637 inline.model.caller = request.user
638 yield inline.get_formset(request, obj)
640 def get_formsets(self, request, obj=None):
641 for inline in self.get_inline_instances(request, obj):
642 # hide MyInline in the add view
645 if isinstance(inline, SliverInline):
646 inline.model.caller = request.user
647 yield inline.get_formset(request, obj)
649 def accountLink(self, obj):
650 link_obj = obj.accounts.all()
652 reverse_path = "admin:core_account_change"
653 url = reverse(reverse_path, args =(link_obj[0].id,))
654 return "<a href='%s'>%s</a>" % (url, "view billing details")
656 return "no billing data for this site"
657 accountLink.allow_tags = True
658 accountLink.short_description = "Billing"
660 def save_model(self, request, obj, form, change):
661 # update openstack connection to use this site/tenant
662 obj.save_by_user(request.user)
664 def delete_model(self, request, obj):
665 obj.delete_by_user(request.user)
668 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
669 fieldList = ['backend_status_text', 'user', 'site', 'role']
671 (None, {'fields': fieldList, 'classes':['collapse']})
673 readonly_fields = ('backend_status_text', )
674 list_display = ('backend_status_icon', 'user', 'site', 'role')
675 list_display_links = list_display
676 user_readonly_fields = fieldList
677 user_readonly_inlines = []
679 def formfield_for_foreignkey(self, db_field, request, **kwargs):
680 if db_field.name == 'site':
681 if not request.user.is_admin:
682 # only show sites where user is an admin or pi
684 for site_privilege in SitePrivilege.objects.filer(user=request.user):
685 if site_privilege.role.role_type in ['admin', 'pi']:
686 sites.add(site_privilege.site)
687 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
689 if db_field.name == 'user':
690 if not request.user.is_admin:
691 # only show users from sites where caller has admin or pi role
692 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
693 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
694 sites = [site_privilege.site for site_privilege in site_privileges]
695 site_privileges = SitePrivilege.objects.filter(site__in=sites)
696 emails = [site_privilege.user.email for site_privilege in site_privileges]
697 users = User.objects.filter(email__in=emails)
698 kwargs['queryset'] = users
700 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
702 def queryset(self, request):
703 # admins can see all privileges. Users can only see privileges at sites
704 # where they have the admin role or pi role.
705 qs = super(SitePrivilegeAdmin, self).queryset(request)
706 #if not request.user.is_admin:
707 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
708 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
709 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
710 # sites = Site.objects.filter(login_base__in=login_bases)
711 # qs = qs.filter(site__in=sites)
714 class SliceForm(forms.ModelForm):
718 'service': LinkedSelect
722 cleaned_data = super(SliceForm, self).clean()
723 name = cleaned_data.get('name')
724 site_id = cleaned_data.get('site')
725 site = Slice.objects.get(id=site_id)
726 if not name.startswith(site.login_base):
727 raise forms.ValidationError('slice name must begin with %s' % site.login_base)
730 class SliceAdmin(PlanetStackBaseAdmin):
732 fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
733 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
734 readonly_fields = ('backend_status_text', )
735 list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_slivers')
736 list_display_links = ('backend_status_icon', 'name', )
737 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
739 user_readonly_fields = fieldList
741 suit_form_tabs =(('general', 'Slice Details'),
742 ('slicenetworks','Networks'),
743 ('sliceprivileges','Privileges'),
744 ('slivers','Slivers'),
746 ('reservations','Reservations'),
749 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
750 deployment_nodes = []
751 for node in Node.objects.all():
752 deployment_nodes.append( (node.deployment.id, node.id, node.name) )
754 deployment_flavors = []
755 for flavor in Flavor.objects.all():
756 for deployment in flavor.deployments.all():
757 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
759 deployment_images = []
760 for image in Image.objects.all():
761 for imageDeployment in image.imagedeployments_set.all():
762 deployment_images.append( (imageDeployment.deployment.id, image.id, image.name) )
764 site_login_bases = []
765 for site in Site.objects.all():
766 site_login_bases.append((site.id, site.login_base))
768 context["deployment_nodes"] = deployment_nodes
769 context["deployment_flavors"] = deployment_flavors
770 context["deployment_images"] = deployment_images
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)