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, AdminTextareaWidget
11 from django.contrib.auth.forms import ReadOnlyPasswordHashField, AdminPasswordChangeForm
12 from django.contrib.auth.signals import user_logged_in
13 from django.utils import timezone
14 from django.contrib.contenttypes import generic
15 from suit.widgets import LinkedSelect
16 from django.core.exceptions import PermissionDenied
17 from django.core.urlresolvers import reverse, NoReverseMatch
18 from django.utils.encoding import force_text, python_2_unicode_compatible
19 from django.utils.html import conditional_escape, format_html
20 from django.forms.utils import flatatt, to_current_timezone
21 from cgi import escape as html_escape
23 import django_evolution
26 # thread locals necessary to work around a django-suit issue
27 _thread_locals = threading.local()
29 def backend_icon(obj): # backend_status, enacted, updated):
30 #return "%s %s %s" % (str(obj.updated), str(obj.enacted), str(obj.backend_status))
31 if (obj.enacted is not None) and obj.enacted >= obj.updated:
32 return '<span style="min-width:16px;"><img src="/static/admin/img/icon_success.gif"></span>'
34 if obj.backend_status == "Provisioning in progress" or obj.backend_status=="":
35 return '<span style="min-width:16px;" title="%s"><img src="/static/admin/img/icon_clock.gif"></span>' % obj.backend_status
37 return '<span style="min-width:16px;" title="%s"><img src="/static/admin/img/icon_error.gif"></span>' % html_escape(obj.backend_status, quote=True)
39 def backend_text(obj):
40 icon = backend_icon(obj)
41 if (obj.enacted is not None) and obj.enacted >= obj.updated:
42 return "%s %s" % (icon, "successfully enacted")
44 return "%s %s" % (icon, html_escape(obj.backend_status, quote=True))
46 class UploadTextareaWidget(AdminTextareaWidget):
47 def render(self, name, value, attrs=None):
50 final_attrs = self.build_attrs(attrs, name=name)
\r
51 return format_html('<input type="file" style="width: 0; height: 0" id="btn_upload_%s" onChange="uploadTextarea(event,\'%s\');">' \
\r
52 '<button onClick="$(\'#btn_upload_%s\').click(); return false;">Upload</button>' \
\r
53 '<br><textarea{0}>\r\n{1}</textarea>' % (attrs["id"], attrs["id"], attrs["id"]),
\r
54 flatatt(final_attrs),
\r
57 class PlainTextWidget(forms.HiddenInput):
60 def render(self, name, value, attrs=None):
63 return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
65 class PermissionCheckingAdminMixin(object):
66 # call save_by_user and delete_by_user instead of save and delete
68 def has_add_permission(self, request, obj=None):
69 return (not self.__user_is_readonly(request))
71 def has_delete_permission(self, request, obj=None):
72 return (not self.__user_is_readonly(request))
74 def save_model(self, request, obj, form, change):
75 if self.__user_is_readonly(request):
76 # this 'if' might be redundant if save_by_user is implemented right
77 raise PermissionDenied
79 obj.caller = request.user
80 # update openstack connection to use this site/tenant
81 obj.save_by_user(request.user)
83 def delete_model(self, request, obj):
84 obj.delete_by_user(request.user)
86 def save_formset(self, request, form, formset, change):
87 instances = formset.save(commit=False)
88 for instance in instances:
89 instance.save_by_user(request.user)
91 # BUG in django 1.7? Objects are not deleted by formset.save if
92 # commit is False. So let's delete them ourselves.
94 # code from forms/models.py save_existing_objects()
96 forms_to_delete = formset.deleted_forms
\r
97 except AttributeError:
\r
99 if formset.initial_forms:
100 for form in formset.initial_forms:
102 if form in forms_to_delete:
105 formset.deleted_objects.append(obj)
110 def get_actions(self,request):
111 actions = super(PermissionCheckingAdminMixin,self).get_actions(request)
113 if self.__user_is_readonly(request):
114 if 'delete_selected' in actions:
115 del actions['delete_selected']
119 def change_view(self,request,object_id, extra_context=None):
120 if self.__user_is_readonly(request):
121 if not hasattr(self, "readonly_save"):
\r
122 # save the original readonly fields
\r
123 self.readonly_save = self.readonly_fields
\r
124 self.inlines_save = self.inlines
\r
125 if hasattr(self, "user_readonly_fields"):
\r
126 self.readonly_fields=self.user_readonly_fields
\r
127 if hasattr(self, "user_readonly_inlines"):
\r
128 self.inlines = self.user_readonly_inlines
\r
130 if hasattr(self, "readonly_save"):
\r
131 # restore the original readonly fields
\r
132 self.readonly_fields = self.readonly_save
\r
133 if hasattr(self, "inlines_save"):
\r
134 self.inlines = self.inlines_save
137 return super(PermissionCheckingAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
138 except PermissionDenied:
140 if request.method == 'POST':
141 raise PermissionDenied
142 request.readonly = True
143 return super(PermissionCheckingAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
145 def __user_is_readonly(self, request):
146 return request.user.isReadOnlyUser()
148 def backend_status_text(self, obj):
149 return mark_safe(backend_text(obj))
151 def backend_status_icon(self, obj):
152 return mark_safe(backend_icon(obj))
153 backend_status_icon.short_description = ""
155 def get_form(self, request, obj=None, **kwargs):
156 # Save obj and request in thread-local storage, so suit_form_tabs can
157 # use it to determine whether we're in edit or add mode, and can
158 # determine whether the user is an admin.
159 _thread_locals.request = request
160 _thread_locals.obj = obj
161 return super(PermissionCheckingAdminMixin, self).get_form(request, obj, **kwargs)
163 def get_inline_instances(self, request, obj=None):
164 inlines = super(PermissionCheckingAdminMixin, self).get_inline_instances(request, obj)
166 # inlines that should only be shown to an admin user
167 if request.user.is_admin:
168 for inline_class in getattr(self, "admin_inlines", []):
169 inlines.append(inline_class(self.model, self.admin_site))
173 class ReadOnlyAwareAdmin(PermissionCheckingAdminMixin, admin.ModelAdmin):
174 # Note: Make sure PermissionCheckingAdminMixin is listed before
175 # admin.ModelAdmin in the class declaration.
179 class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
182 class SingletonAdmin (ReadOnlyAwareAdmin):
183 def has_add_permission(self, request):
184 if not super(SingletonAdmin, self).has_add_permission(request):
187 num_objects = self.model.objects.count()
193 class PlStackTabularInline(admin.TabularInline):
194 def __init__(self, *args, **kwargs):
195 super(PlStackTabularInline, self).__init__(*args, **kwargs)
197 # InlineModelAdmin as no get_fields() method, so in order to add
198 # the selflink field, we override __init__ to modify self.fields and
199 # self.readonly_fields.
201 self.setup_selflink()
203 def get_change_url(self, model, id):
204 """ Get the URL to a change form in the admin for this model """
205 reverse_path = "admin:%s_change" % (model._meta.db_table)
207 url = reverse(reverse_path, args=(id,))
208 except NoReverseMatch:
213 def setup_selflink(self):
214 if hasattr(self, "selflink_fieldname"):
215 """ self.selflink_model can be defined to punch through a relation
216 to its target object. For example, in SliceNetworkInline, set
217 selflink_model = "network", and the URL will lead to the Network
218 object instead of trying to bring up a change view of the
221 self.selflink_model = getattr(self.model,self.selflink_fieldname).field.rel.to
223 self.selflink_model = self.model
225 url = self.get_change_url(self.selflink_model, 0)
227 # We don't have an admin for this object, so don't create the
232 # Since we need to add "selflink" to the field list, we need to create
233 # self.fields if it is None.
234 if (self.fields is None):
236 for f in self.model._meta.fields:
237 if f.editable and f.name != "id":
238 self.fields.append(f.name)
240 self.fields = tuple(self.fields) + ("selflink", )
242 if self.readonly_fields is None:
243 self.readonly_fields = ()
245 self.readonly_fields = tuple(self.readonly_fields) + ("selflink", )
247 def selflink(self, obj):
248 if hasattr(self, "selflink_fieldname"):
249 obj = getattr(obj, self.selflink_fieldname)
252 url = self.get_change_url(self.selflink_model, obj.id)
253 return "<a href='%s'>Details</a>" % str(url)
255 return "Not present"
\r
257 selflink.allow_tags = True
258 selflink.short_description = "Details"
260 def has_add_permission(self, request):
261 return not request.user.isReadOnlyUser()
263 def get_readonly_fields(self, request, obj=None):
264 readonly_fields = list(self.readonly_fields)[:]
265 if request.user.isReadOnlyUser():
266 for field in self.fields:
267 if not field in readonly_fields:
268 readonly_fields.append(field)
269 return readonly_fields
271 def backend_status_icon(self, obj):
272 return mark_safe(backend_icon(obj))
273 backend_status_icon.short_description = ""
275 class PlStackGenericTabularInline(generic.GenericTabularInline):
276 def has_add_permission(self, request):
277 return not request.user.isReadOnlyUser()
279 def get_readonly_fields(self, request, obj=None):
280 readonly_fields = list(self.readonly_fields)[:]
281 if request.user.isReadOnlyUser():
282 for field in self.fields:
283 if not field in readonly_fields:
284 readonly_fields.append(field)
285 return readonly_fields
287 def backend_status_icon(self, obj):
288 return mark_safe(backend_icon(obj))
289 backend_status_icon.short_description = ""
291 class ReservationInline(PlStackTabularInline):
294 suit_classes = 'suit-tab suit-tab-reservations'
296 def queryset(self, request):
297 return Reservation.select_by_user(request.user)
299 class TagInline(PlStackGenericTabularInline):
302 suit_classes = 'suit-tab suit-tab-tags'
303 fields = ['service', 'name', 'value']
305 def queryset(self, request):
306 return Tag.select_by_user(request.user)
308 class NetworkLookerUpper:
309 """ This is a callable that looks up a network name in a sliver and returns
310 the ip address for that network.
313 byNetworkName = {} # class variable
315 def __init__(self, name):
316 self.short_description = name
318 self.network_name = name
320 def __call__(self, obj):
322 for nbs in obj.networksliver_set.all():
323 if (nbs.network.name == self.network_name):
328 return self.network_name
331 def get(network_name):
332 """ We want to make sure we alwars return the same NetworkLookerUpper
333 because sometimes django will cause them to be instantiated multiple
334 times (and we don't want different ones in form.fields vs
335 SliverInline.readonly_fields).
337 if network_name not in NetworkLookerUpper.byNetworkName:
338 NetworkLookerUpper.byNetworkName[network_name] = NetworkLookerUpper(network_name)
339 return NetworkLookerUpper.byNetworkName[network_name]
341 class SliverInline(PlStackTabularInline):
343 fields = ['backend_status_icon', 'all_ips_string', 'instance_name', 'slice', 'deploymentNetwork', 'flavor', 'image', 'node']
345 readonly_fields = ['backend_status_icon', 'all_ips_string', 'instance_name']
346 suit_classes = 'suit-tab suit-tab-slivers'
348 def queryset(self, request):
349 return Sliver.select_by_user(request.user)
351 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
352 if db_field.name == 'deploymentNetwork':
353 kwargs['queryset'] = Deployment.select_by_acl(request.user)
354 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_deployment_changed(this);"})
355 elif db_field.name == 'flavor':
356 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_flavor_changed(this);"})
358 field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
362 class SiteInline(PlStackTabularInline):
365 suit_classes = 'suit-tab suit-tab-sites'
367 def queryset(self, request):
368 return Site.select_by_user(request.user)
370 class UserInline(PlStackTabularInline):
372 fields = ['backend_status_icon', 'email', 'firstname', 'lastname']
373 readonly_fields = ('backend_status_icon', )
375 suit_classes = 'suit-tab suit-tab-users'
377 def queryset(self, request):
378 return User.select_by_user(request.user)
380 class SliceInline(PlStackTabularInline):
382 fields = ['backend_status_icon', 'name', 'site', 'serviceClass', 'service']
383 readonly_fields = ('backend_status_icon', )
385 suit_classes = 'suit-tab suit-tab-slices'
387 def queryset(self, request):
388 return Slice.select_by_user(request.user)
390 class NodeInline(PlStackTabularInline):
393 suit_classes = 'suit-tab suit-tab-nodes'
394 fields = ['backend_status_icon', 'name','deployment','site']
395 readonly_fields = ('backend_status_icon', )
397 class DeploymentPrivilegeInline(PlStackTabularInline):
398 model = DeploymentPrivilege
400 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
401 fields = ['backend_status_icon', 'user','role','deployment']
402 readonly_fields = ('backend_status_icon', )
404 def queryset(self, request):
405 return DeploymentPrivilege.select_by_user(request.user)
407 class SitePrivilegeInline(PlStackTabularInline):
408 model = SitePrivilege
410 suit_classes = 'suit-tab suit-tab-siteprivileges'
411 fields = ['backend_status_icon', 'user','site', 'role']
412 readonly_fields = ('backend_status_icon', )
414 def formfield_for_foreignkey(self, db_field, request, **kwargs):
415 if db_field.name == 'site':
416 kwargs['queryset'] = Site.select_by_user(request.user)
418 if db_field.name == 'user':
419 kwargs['queryset'] = User.select_by_user(request.user)
420 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
422 def queryset(self, request):
423 return SitePrivilege.select_by_user(request.user)
425 class SiteDeploymentsInline(PlStackTabularInline):
426 model = SiteDeployments
428 suit_classes = 'suit-tab suit-tab-deployments'
429 fields = ['backend_status_icon', 'deployment','site']
430 readonly_fields = ('backend_status_icon', )
432 def formfield_for_foreignkey(self, db_field, request, **kwargs):
433 if db_field.name == 'site':
434 kwargs['queryset'] = Site.select_by_user(request.user)
436 if db_field.name == 'deployment':
437 kwargs['queryset'] = Deployment.select_by_user(request.user)
438 return super(SiteDeploymentsInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
440 def queryset(self, request):
441 return SiteDeployments.select_by_user(request.user)
444 class SlicePrivilegeInline(PlStackTabularInline):
445 model = SlicePrivilege
446 suit_classes = 'suit-tab suit-tab-sliceprivileges'
448 fields = ('backend_status_icon', 'user', 'slice', 'role')
449 readonly_fields = ('backend_status_icon', )
451 def formfield_for_foreignkey(self, db_field, request, **kwargs):
452 if db_field.name == 'slice':
453 kwargs['queryset'] = Slice.select_by_user(request.user)
454 if db_field.name == 'user':
455 kwargs['queryset'] = User.select_by_user(request.user)
457 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
459 def queryset(self, request):
460 return SlicePrivilege.select_by_user(request.user)
462 class SliceNetworkInline(PlStackTabularInline):
463 model = Network.slices.through
464 selflink_fieldname = "network"
466 verbose_name = "Network Connection"
467 verbose_name_plural = "Network Connections"
468 suit_classes = 'suit-tab suit-tab-slicenetworks'
469 fields = ['backend_status_icon', 'network']
470 readonly_fields = ('backend_status_icon', )
472 class ImageDeploymentsInline(PlStackTabularInline):
473 model = ImageDeployments
475 verbose_name = "Image Deployments"
476 verbose_name_plural = "Image Deployments"
477 suit_classes = 'suit-tab suit-tab-imagedeployments'
478 fields = ['backend_status_icon', 'image', 'deployment', 'glance_image_id']
479 readonly_fields = ['backend_status_icon', 'glance_image_id']
481 class SliceRoleAdmin(PlanetStackBaseAdmin):
485 class SiteRoleAdmin(PlanetStackBaseAdmin):
489 class DeploymentAdminForm(forms.ModelForm):
490 sites = forms.ModelMultipleChoiceField(
491 queryset=Site.objects.all(),
493 help_text="Select which sites are allowed to host nodes in this deployment",
494 widget=FilteredSelectMultiple(
495 verbose_name=('Sites'), is_stacked=False
498 images = forms.ModelMultipleChoiceField(
499 queryset=Image.objects.all(),
501 help_text="Select which images should be deployed on this deployment",
502 widget=FilteredSelectMultiple(
503 verbose_name=('Images'), is_stacked=False
506 flavors = forms.ModelMultipleChoiceField(
507 queryset=Flavor.objects.all(),
509 help_text="Select which flavors should be usable on this deployment",
510 widget=FilteredSelectMultiple(
511 verbose_name=('Flavors'), is_stacked=False
516 many_to_many = ["flavors",]
518 def __init__(self, *args, **kwargs):
519 request = kwargs.pop('request', None)
520 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
522 self.fields['accessControl'].initial = "allow site " + request.user.site.name
524 if self.instance and self.instance.pk:
525 self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments.all()]
526 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments.all()]
527 self.fields['flavors'].initial = self.instance.flavors.all()
529 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
530 """ helper function for handling m2m relations from the MultipleChoiceField
532 this_obj: the source object we want to link from
534 selected_objs: a list of destination objects we want to link to
536 all_relations: the full set of relations involving this_obj, including ones we don't want
538 relation_class: the class that implements the relation from source to dest
540 local_attrname: field name representing this_obj in relation_class
542 foreign_attrname: field name representing selected_objs in relation_class
544 This function will remove all newobjclass relations from this_obj
545 that are not contained in selected_objs, and add any relations that
546 are in selected_objs but don't exist in the data model yet.
549 existing_dest_objs = []
550 for relation in list(all_relations):
551 if getattr(relation, foreign_attrname) not in selected_objs:
552 #print "deleting site", sdp.site
555 existing_dest_objs.append(getattr(relation, foreign_attrname))
557 for dest_obj in selected_objs:
558 if dest_obj not in existing_dest_objs:
559 #print "adding site", site
560 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
561 relation = relation_class(**kwargs)
564 def save(self, commit=True):
565 deployment = super(DeploymentAdminForm, self).save(commit=False)
569 # this has to be done after save() if/when a deployment is first created
570 deployment.flavors = self.cleaned_data['flavors']
573 # save_m2m() doesn't seem to work with 'through' relations. So we
574 # create/destroy the through models ourselves. There has to be
577 self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments.all(), SiteDeployments, "deployment", "site")
578 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments.all(), ImageDeployments, "deployment", "image")
584 class DeploymentAdminROForm(DeploymentAdminForm):
585 def save(self, commit=True):
586 raise PermissionDenied
588 class SiteAssocInline(PlStackTabularInline):
589 model = Site.deployments.through
591 suit_classes = 'suit-tab suit-tab-sites'
593 class DeploymentAdmin(PlanetStackBaseAdmin):
595 fieldList = ['backend_status_text', 'name', 'availability_zone', 'sites', 'images', 'flavors', 'accessControl']
596 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
597 inlines = [DeploymentPrivilegeInline,NodeInline,TagInline] # ,ImageDeploymentsInline]
598 list_display = ['backend_status_icon', 'name']
599 list_display_links = ('backend_status_icon', 'name', )
600 readonly_fields = ('backend_status_text', )
602 user_readonly_fields = ['name']
604 suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags')) # ,('imagedeployments','Images'))
606 def get_form(self, request, obj=None, **kwargs):
607 if request.user.isReadOnlyUser():
608 kwargs["form"] = DeploymentAdminROForm
610 kwargs["form"] = DeploymentAdminForm
611 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
613 # from stackexchange: pass the request object into the form
615 class AdminFormMetaClass(adminForm):
616 def __new__(cls, *args, **kwargs):
617 kwargs['request'] = request
618 return adminForm(*args, **kwargs)
620 return AdminFormMetaClass
622 class ServiceAttrAsTabInline(PlStackTabularInline):
623 model = ServiceAttribute
624 fields = ['name','value']
626 suit_classes = 'suit-tab suit-tab-serviceattrs'
628 class ServiceAdmin(PlanetStackBaseAdmin):
629 list_display = ("backend_status_icon","name","description","versionNumber","enabled","published")
630 list_display_links = ('backend_status_icon', 'name', )
631 fieldList = ["backend_status_text","name","description","versionNumber","enabled","published"]
632 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
633 inlines = [ServiceAttrAsTabInline,SliceInline]
634 readonly_fields = ('backend_status_text', )
636 user_readonly_fields = fieldList
638 suit_form_tabs =(('general', 'Service Details'),
640 ('serviceattrs','Additional Attributes'),
643 class SiteAdmin(PlanetStackBaseAdmin):
644 fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
646 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
647 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
649 suit_form_tabs =(('general', 'Site Details'),
651 ('siteprivileges','Privileges'),
652 ('deployments','Deployments'),
657 readonly_fields = ['backend_status_text', 'accountLink']
659 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
661 list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
662 list_display_links = ('backend_status_icon', 'name', )
663 filter_horizontal = ('deployments',)
664 inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentsInline]
665 search_fields = ['name']
667 def queryset(self, request):
668 return Site.select_by_user(request.user)
670 def get_formsets(self, request, obj=None):
671 for inline in self.get_inline_instances(request, obj):
672 # hide MyInline in the add view
675 if isinstance(inline, SliverInline):
676 inline.model.caller = request.user
677 yield inline.get_formset(request, obj)
679 def accountLink(self, obj):
680 link_obj = obj.accounts.all()
682 reverse_path = "admin:core_account_change"
683 url = reverse(reverse_path, args =(link_obj[0].id,))
684 return "<a href='%s'>%s</a>" % (url, "view billing details")
686 return "no billing data for this site"
687 accountLink.allow_tags = True
688 accountLink.short_description = "Billing"
690 def save_model(self, request, obj, form, change):
691 # update openstack connection to use this site/tenant
692 obj.save_by_user(request.user)
694 def delete_model(self, request, obj):
695 obj.delete_by_user(request.user)
698 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
699 fieldList = ['backend_status_text', 'user', 'site', 'role']
701 (None, {'fields': fieldList, 'classes':['collapse']})
703 readonly_fields = ('backend_status_text', )
704 list_display = ('backend_status_icon', 'user', 'site', 'role')
705 list_display_links = list_display
706 user_readonly_fields = fieldList
707 user_readonly_inlines = []
709 def formfield_for_foreignkey(self, db_field, request, **kwargs):
710 if db_field.name == 'site':
711 if not request.user.is_admin:
712 # only show sites where user is an admin or pi
714 for site_privilege in SitePrivilege.objects.filer(user=request.user):
715 if site_privilege.role.role_type in ['admin', 'pi']:
716 sites.add(site_privilege.site)
717 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
719 if db_field.name == 'user':
720 if not request.user.is_admin:
721 # only show users from sites where caller has admin or pi role
722 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
723 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
724 sites = [site_privilege.site for site_privilege in site_privileges]
725 site_privileges = SitePrivilege.objects.filter(site__in=sites)
726 emails = [site_privilege.user.email for site_privilege in site_privileges]
727 users = User.objects.filter(email__in=emails)
728 kwargs['queryset'] = users
730 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
732 def queryset(self, request):
733 # admins can see all privileges. Users can only see privileges at sites
734 # where they have the admin role or pi role.
735 qs = super(SitePrivilegeAdmin, self).queryset(request)
736 #if not request.user.is_admin:
737 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
738 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
739 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
740 # sites = Site.objects.filter(login_base__in=login_bases)
741 # qs = qs.filter(site__in=sites)
744 class SliceForm(forms.ModelForm):
748 'service': LinkedSelect
752 cleaned_data = super(SliceForm, self).clean()
753 name = cleaned_data.get('name')
754 site = cleaned_data.get('site')
755 slice_id = self.instance.id
756 if not site and slice_id:
757 site = Slice.objects.get(id=slice_id).site
758 if (not isinstance(site,Site)):
759 # previous code indicates 'site' could be a site_id and not a site?
760 site = Slice.objects.get(id=site.id)
761 if not name.startswith(site.login_base):
762 raise forms.ValidationError('slice name must begin with %s' % site.login_base)
765 class SliceDeploymentsInline(PlStackTabularInline):
766 model = SliceDeployments
768 verbose_name = "Slice Deployment"
769 verbose_name_plural = "Slice Deployments"
770 suit_classes = 'suit-tab suit-tab-admin-only'
771 fields = ['backend_status_icon', 'deployment', 'tenant_id']
772 readonly_fields = ('backend_status_icon', )
774 class SliceAdmin(PlanetStackBaseAdmin):
776 fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
777 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
778 readonly_fields = ('backend_status_text', )
779 list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_slivers')
780 list_display_links = ('backend_status_icon', 'name', )
781 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
782 admin_inlines = [SliceDeploymentsInline]
784 user_readonly_fields = fieldList
787 def suit_form_tabs(self):
788 tabs =[('general', 'Slice Details'),
789 ('slicenetworks','Networks'),
790 ('sliceprivileges','Privileges'),
791 ('slivers','Slivers'),
793 ('reservations','Reservations'),
796 request=getattr(_thread_locals, "request", None)
797 if request and request.user.is_admin:
798 tabs.append( ('admin-only', 'Admin-Only') )
802 def add_view(self, request, form_url='', extra_context=None):
803 # revert to default read-only fields
804 self.readonly_fields = ('backend_status_text',)
805 return super(SliceAdmin, self).add_view(request, form_url, extra_context=extra_context)
807 def change_view(self, request, object_id, form_url='', extra_context=None):
809 # cannot change the site of an existing slice so make the site field read only
811 self.readonly_fields = ('backend_status_text','site')
812 return super(SliceAdmin, self).change_view(request, object_id, form_url)
814 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
815 deployment_nodes = []
816 for node in Node.objects.all():
817 deployment_nodes.append( (node.deployment.id, node.id, node.name) )
819 deployment_flavors = []
820 for flavor in Flavor.objects.all():
821 for deployment in flavor.deployments.all():
822 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
824 deployment_images = []
825 for image in Image.objects.all():
826 for imageDeployment in image.imagedeployments.all():
827 deployment_images.append( (imageDeployment.deployment.id, image.id, image.name) )
829 site_login_bases = []
830 for site in Site.objects.all():
831 site_login_bases.append((site.id, site.login_base))
833 context["deployment_nodes"] = deployment_nodes
834 context["deployment_flavors"] = deployment_flavors
835 context["deployment_images"] = deployment_images
836 context["site_login_bases"] = site_login_bases
837 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
839 def formfield_for_foreignkey(self, db_field, request, **kwargs):
840 if db_field.name == 'site':
841 kwargs['queryset'] = Site.select_by_user(request.user)
842 kwargs['widget'] = forms.Select(attrs={'onChange': "update_slice_prefix(this, $($(this).closest('fieldset')[0]).find('.field-name input')[0].id)"})
844 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
846 def queryset(self, request):
847 # admins can see all keys. Users can only see slices they belong to.
848 return Slice.select_by_user(request.user)
850 def get_formsets(self, request, obj=None):
851 for inline in self.get_inline_instances(request, obj):
852 # hide MyInline in the add view
855 if isinstance(inline, SliverInline):
856 inline.model.caller = request.user
857 yield inline.get_formset(request, obj)
859 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
861 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
863 readonly_fields = ('backend_status_text', )
864 list_display = ('backend_status_icon', 'user', 'slice', 'role')
865 list_display_links = list_display
867 user_readonly_fields = ['user', 'slice', 'role']
868 user_readonly_inlines = []
870 def formfield_for_foreignkey(self, db_field, request, **kwargs):
871 if db_field.name == 'slice':
872 kwargs['queryset'] = Slice.select_by_user(request.user)
874 if db_field.name == 'user':
875 kwargs['queryset'] = User.select_by_user(request.user)
877 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
879 def queryset(self, request):
880 # admins can see all memberships. Users can only see memberships of
881 # slices where they have the admin role.
882 return SlicePrivilege.select_by_user(request.user)
884 def save_model(self, request, obj, form, change):
885 # update openstack connection to use this site/tenant
886 auth = request.session.get('auth', {})
887 auth['tenant'] = obj.slice.slicename
888 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
891 def delete_model(self, request, obj):
892 # update openstack connection to use this site/tenant
893 auth = request.session.get('auth', {})
894 auth['tenant'] = obj.slice.slicename
895 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
899 class ImageAdmin(PlanetStackBaseAdmin):
901 fieldsets = [('Image Details',
902 {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
903 'classes': ['suit-tab suit-tab-general']})
905 readonly_fields = ('backend_status_text', )
907 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
909 inlines = [SliverInline, ImageDeploymentsInline]
911 user_readonly_fields = ['name', 'disk_format', 'container_format']
913 list_display = ['backend_status_icon', 'name']
914 list_display_links = ('backend_status_icon', 'name', )
916 class NodeForm(forms.ModelForm):
919 'site': LinkedSelect,
920 'deployment': LinkedSelect
923 class NodeAdmin(PlanetStackBaseAdmin):
925 list_display = ('backend_status_icon', 'name', 'site', 'deployment')
926 list_display_links = ('backend_status_icon', 'name', )
927 list_filter = ('deployment',)
929 inlines = [TagInline,SliverInline]
930 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
931 readonly_fields = ('backend_status_text', )
933 user_readonly_fields = ['name','site','deployment']
934 user_readonly_inlines = [TagInline,SliverInline]
936 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
939 class SliverForm(forms.ModelForm):
942 ip = forms.CharField(widget=PlainTextWidget)
943 instance_name = forms.CharField(widget=PlainTextWidget)
945 'ip': PlainTextWidget(),
946 'instance_name': PlainTextWidget(),
947 'slice': LinkedSelect,
948 'deploymentNetwork': LinkedSelect,
949 'node': LinkedSelect,
950 'image': LinkedSelect
953 class TagAdmin(PlanetStackBaseAdmin):
954 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
955 list_display_links = list_display
956 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
957 user_readonly_inlines = []
959 class SliverAdmin(PlanetStackBaseAdmin):
962 ('Sliver Details', {'fields': ['backend_status_text', 'slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
964 readonly_fields = ('backend_status_text', )
965 list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'flavor', 'image', 'node', 'deploymentNetwork']
966 list_display_links = ('backend_status_icon', 'ip',)
968 suit_form_tabs =(('general', 'Sliver Details'),
972 inlines = [TagInline]
974 user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image']
976 def formfield_for_foreignkey(self, db_field, request, **kwargs):
977 if db_field.name == 'slice':
978 kwargs['queryset'] = Slice.select_by_user(request.user)
980 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
982 def queryset(self, request):
983 # admins can see all slivers. Users can only see slivers of
984 # the slices they belong to.
985 return Sliver.select_by_user(request.user)
988 def get_formsets(self, request, obj=None):
989 # make some fields read only if we are updating an existing record
991 #self.readonly_fields = ('ip', 'instance_name')
992 self.readonly_fields = ('backend_status_text',)
994 self.readonly_fields = ('backend_status_text',)
995 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
997 for inline in self.get_inline_instances(request, obj):
998 # hide MyInline in the add view
1001 if isinstance(inline, SliverInline):
1002 inline.model.caller = request.user
1003 yield inline.get_formset(request, obj)
1005 #def save_model(self, request, obj, form, change):
1006 # # update openstack connection to use this site/tenant
1007 # auth = request.session.get('auth', {})
1008 # auth['tenant'] = obj.slice.name
1009 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1010 # obj.creator = request.user
1013 #def delete_model(self, request, obj):
1014 # # update openstack connection to use this site/tenant
1015 # auth = request.session.get('auth', {})
1016 # auth['tenant'] = obj.slice.name
1017 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1020 class UserCreationForm(forms.ModelForm):
1021 """A form for creating new users. Includes all the required
1022 fields, plus a repeated password."""
1023 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
1024 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
1028 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
1030 def clean_password2(self):
1031 # Check that the two password entries match
1032 password1 = self.cleaned_data.get("password1")
1033 password2 = self.cleaned_data.get("password2")
1034 if password1 and password2 and password1 != password2:
1035 raise forms.ValidationError("Passwords don't match")
1038 def save(self, commit=True):
1039 # Save the provided password in hashed format
1040 user = super(UserCreationForm, self).save(commit=False)
1041 user.password = self.cleaned_data["password1"]
1042 #user.set_password(self.cleaned_data["password1"])
1048 class UserChangeForm(forms.ModelForm):
1049 """A form for updating users. Includes all the fields on
1050 the user, but replaces the password field with admin's
1051 password hash display field.
1053 password = ReadOnlyPasswordHashField(label='Password',
1054 help_text= '<a href=\"password/\">Change Password</a>.')
1058 widgets = { 'public_key': UploadTextareaWidget, }
1060 def clean_password(self):
1061 # Regardless of what the user provides, return the initial value.
1062 # This is done here, rather than on the field, because the
1063 # field does not have access to the initial value
1064 return self.initial["password"]
1066 class UserDashboardViewInline(PlStackTabularInline):
1067 model = UserDashboardView
1069 suit_classes = 'suit-tab suit-tab-dashboards'
1070 fields = ['user', 'dashboardView', 'order']
1072 class UserAdmin(PermissionCheckingAdminMixin, UserAdmin):
1073 # Note: Make sure PermissionCheckingAdminMixin is listed before
1074 # admin.ModelAdmin in the class declaration.
1079 # The forms to add and change user instances
1080 form = UserChangeForm
1081 add_form = UserCreationForm
1083 # The fields to be used in displaying the User model.
1084 # These override the definitions on the base UserAdmin
1085 # that reference specific fields on auth.User.
1086 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
1087 list_filter = ('site',)
1088 inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
1090 fieldListLoginDetails = ['backend_status_text', 'email','site','password','is_active','is_readonly','is_admin','public_key']
1091 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1094 ('Login Details', {'fields': ['backend_status_text', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1095 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1096 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1097 #('Important dates', {'fields': ('last_login',)}),
1101 'classes': ('wide',),
1102 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')},
1105 readonly_fields = ('backend_status_text', )
1106 search_fields = ('email',)
1107 ordering = ('email',)
1108 filter_horizontal = ()
1110 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1113 def suit_form_tabs(self):
1114 if getattr(_thread_locals, "obj", None) is None:
1117 return (('general','Login Details'),
1118 ('contact','Contact Information'),
1119 ('sliceprivileges','Slice Privileges'),
1120 ('siteprivileges','Site Privileges'),
1121 ('deploymentprivileges','Deployment Privileges'),
1122 ('dashboards','Dashboard Views'))
1124 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1125 if db_field.name == 'site':
1126 kwargs['queryset'] = Site.select_by_user(request.user)
1128 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1130 def queryset(self, request):
1131 return User.select_by_user(request.user)
1133 class DashboardViewAdmin(PlanetStackBaseAdmin):
1134 fieldsets = [('Dashboard View Details',
1135 {'fields': ['backend_status_text', 'name', 'url'],
1136 'classes': ['suit-tab suit-tab-general']})
1138 readonly_fields = ('backend_status_text', )
1140 suit_form_tabs =(('general','Dashboard View Details'),)
1142 class ServiceResourceInline(PlStackTabularInline):
1143 model = ServiceResource
1146 class ServiceClassAdmin(PlanetStackBaseAdmin):
1147 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1148 list_display_links = ('backend_status_icon', 'name', )
1149 inlines = [ServiceResourceInline]
1151 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1152 user_readonly_inlines = []
1154 class ReservedResourceInline(PlStackTabularInline):
1155 model = ReservedResource
1157 suit_classes = 'suit-tab suit-tab-reservedresources'
1159 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1160 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1162 if db_field.name == 'resource':
1163 # restrict resources to those that the slice's service class allows
1164 if request._slice is not None:
1165 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1166 if len(field.queryset) > 0:
1167 field.initial = field.queryset.all()[0]
1169 field.queryset = field.queryset.none()
\r
1170 elif db_field.name == 'sliver':
\r
1171 # restrict slivers to those that belong to the slice
\r
1172 if request._slice is not None:
\r
1173 field.queryset = field.queryset.filter(slice = request._slice)
1175 field.queryset = field.queryset.none()
\r
1179 def queryset(self, request):
1180 return ReservedResource.select_by_user(request.user)
1182 class ReservationChangeForm(forms.ModelForm):
1186 'slice' : LinkedSelect
1189 class ReservationAddForm(forms.ModelForm):
1190 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1191 refresh = forms.CharField(widget=forms.HiddenInput())
1194 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1196 def clean_slice(self):
1197 slice = self.cleaned_data.get("slice")
1198 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1200 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1206 'slice' : LinkedSelect
1210 class ReservationAddRefreshForm(ReservationAddForm):
1211 """ This form is displayed when the Reservation Form receives an update
1212 from the Slice dropdown onChange handler. It doesn't validate the
1213 data and doesn't save the data. This will cause the form to be
1217 """ don't validate anything other than slice """
1218 dont_validate_fields = ("startTime", "duration")
1220 def full_clean(self):
1221 result = super(ReservationAddForm, self).full_clean()
1223 for fieldname in self.dont_validate_fields:
1224 if fieldname in self._errors:
1225 del self._errors[fieldname]
1229 """ don't save anything """
1233 class ReservationAdmin(PlanetStackBaseAdmin):
1234 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
1235 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1236 readonly_fields = ('backend_status_text', )
1237 list_display = ('startTime', 'duration')
1238 form = ReservationAddForm
1240 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1242 inlines = [ReservedResourceInline]
1243 user_readonly_fields = fieldList
1245 def add_view(self, request, form_url='', extra_context=None):
1246 timezone.activate(request.user.timezone)
1247 request._refresh = False
1248 request._slice = None
1249 if request.method == 'POST':
1250 # "refresh" will be set to "1" if the form was submitted due to
1251 # a change in the Slice dropdown.
1252 if request.POST.get("refresh","1") == "1":
1253 request._refresh = True
1254 request.POST["refresh"] = "0"
1256 # Keep track of the slice that was selected, so the
1257 # reservedResource inline can filter items for the slice.
1258 request._slice = request.POST.get("slice",None)
1259 if (request._slice is not None):
1260 request._slice = Slice.objects.get(id=request._slice)
1262 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1265 def changelist_view(self, request, extra_context = None):
1266 timezone.activate(request.user.timezone)
1267 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1269 def get_form(self, request, obj=None, **kwargs):
1272 # For changes, set request._slice to the slice already set in the
1274 request._slice = obj.slice
1275 self.form = ReservationChangeForm
1277 if getattr(request, "_refresh", False):
1278 self.form = ReservationAddRefreshForm
1280 self.form = ReservationAddForm
1281 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1283 def get_readonly_fields(self, request, obj=None):
1284 if (obj is not None):
1285 # Prevent slice from being changed after the reservation has been
1291 def queryset(self, request):
1292 return Reservation.select_by_user(request.user)
1294 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1295 list_display = ("backend_status_icon", "name", )
1296 list_display_links = ('backend_status_icon', 'name', )
1297 user_readonly_fields = ['name']
1298 user_readonly_inlines = []
1300 class RouterAdmin(PlanetStackBaseAdmin):
1301 list_display = ("backend_status_icon", "name", )
1302 list_display_links = ('backend_status_icon', 'name', )
1303 user_readonly_fields = ['name']
1304 user_readonly_inlines = []
1306 class RouterInline(PlStackTabularInline):
1307 model = Router.networks.through
1309 verbose_name_plural = "Routers"
1310 verbose_name = "Router"
1311 suit_classes = 'suit-tab suit-tab-routers'
1313 class NetworkParameterInline(PlStackGenericTabularInline):
1314 model = NetworkParameter
1316 verbose_name_plural = "Parameters"
1317 verbose_name = "Parameter"
1318 suit_classes = 'suit-tab suit-tab-netparams'
1319 fields = ['backend_status_icon', 'parameter', 'value']
1320 readonly_fields = ('backend_status_icon', )
1322 class NetworkSliversInline(PlStackTabularInline):
1323 fields = ['backend_status_icon', 'network','sliver','ip']
1324 readonly_fields = ("backend_status_icon", "ip", )
1325 model = NetworkSliver
1326 selflink_fieldname = "sliver"
1328 verbose_name_plural = "Slivers"
1329 verbose_name = "Sliver"
1330 suit_classes = 'suit-tab suit-tab-networkslivers'
1332 class NetworkSlicesInline(PlStackTabularInline):
1333 model = NetworkSlice
1334 selflink_fieldname = "slice"
1336 verbose_name_plural = "Slices"
1337 verbose_name = "Slice"
1338 suit_classes = 'suit-tab suit-tab-networkslices'
1339 fields = ['backend_status_icon', 'network','slice']
1340 readonly_fields = ('backend_status_icon', )
1342 class NetworkDeploymentsInline(PlStackTabularInline):
1343 model = NetworkDeployments
1345 verbose_name_plural = "Network Deployments"
1346 verbose_name = "Network Deployment"
1347 suit_classes = 'suit-tab suit-tab-admin-only'
1348 fields = ['backend_status_icon', 'deployment','net_id','subnet_id']
1349 readonly_fields = ('backend_status_icon', )
1351 class NetworkForm(forms.ModelForm):
1355 'topologyParameters': UploadTextareaWidget,
1356 'controllerParameters': UploadTextareaWidget,
1359 class NetworkAdmin(PlanetStackBaseAdmin):
1360 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1361 list_display_links = ('backend_status_icon', 'name', )
1362 readonly_fields = ("subnet", )
1364 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1365 admin_inlines = [NetworkDeploymentsInline]
1370 (None, {'fields': ['backend_status_text', 'name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet'],
1371 'classes':['suit-tab suit-tab-general']}),
1372 (None, {'fields': ['topologyParameters', 'controllerUrl', 'controllerParameters'],
1373 'classes':['suit-tab suit-tab-sdn']}),
1376 readonly_fields = ('backend_status_text', )
1377 user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1380 def suit_form_tabs(self):
1381 tabs=[('general','Network Details'),
1382 ('sdn', 'SDN Configuration'),
1383 ('netparams', 'Parameters'),
1384 ('networkslivers','Slivers'),
1385 ('networkslices','Slices'),
1386 ('routers','Routers'),
1389 request=getattr(_thread_locals, "request", None)
1390 if request and request.user.is_admin:
1391 tabs.append( ('admin-only', 'Admin-Only') )
1396 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1397 list_display = ("backend_status_icon", "name", "guaranteedBandwidth", "visibility")
1398 list_display_links = ('backend_status_icon', 'name', )
1399 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1400 user_readonly_inlines = []
1402 (None, {'fields': ['name', 'description', 'guaranteedBandwidth', 'visibility', 'translation', 'sharedNetworkName', 'sharedNetworkId', 'topologyKind', 'controllerKind'],
1403 'classes':['suit-tab suit-tab-general']}),]
1404 suit_form_tabs = (('general','Network Template Details'), )
1406 class FlavorAdmin(PlanetStackBaseAdmin):
1407 list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1408 list_display_links = ("backend_status_icon", "name")
1409 user_readonly_fields = ("name", "flavor")
1410 fields = ("name", "description", "flavor", "order", "default")
1412 # register a signal that caches the user's credentials when they log in
1413 def cache_credentials(sender, user, request, **kwds):
1414 auth = {'username': request.POST['username'],
1415 'password': request.POST['password']}
1416 request.session['auth'] = auth
1417 user_logged_in.connect(cache_credentials)
1419 def dollar_field(fieldName, short_description):
1420 def newFunc(self, obj):
1422 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1424 x=getattr(obj, fieldName, 0.0)
1426 newFunc.short_description = short_description
1429 def right_dollar_field(fieldName, short_description):
1430 def newFunc(self, obj):
1432 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1433 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1435 x=getattr(obj, fieldName, 0.0)
1437 newFunc.short_description = short_description
1438 newFunc.allow_tags = True
1441 class InvoiceChargeInline(PlStackTabularInline):
1444 verbose_name_plural = "Charges"
1445 verbose_name = "Charge"
1446 exclude = ['account']
1447 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1448 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1452 dollar_amount = right_dollar_field("amount", "Amount")
1454 class InvoiceAdmin(admin.ModelAdmin):
1455 list_display = ("date", "account")
1457 inlines = [InvoiceChargeInline]
1459 fields = ["date", "account", "dollar_amount"]
1460 readonly_fields = ["date", "account", "dollar_amount"]
1462 dollar_amount = dollar_field("amount", "Amount")
1464 class InvoiceInline(PlStackTabularInline):
1467 verbose_name_plural = "Invoices"
1468 verbose_name = "Invoice"
1469 fields = ["date", "dollar_amount"]
1470 readonly_fields = ["date", "dollar_amount"]
1471 suit_classes = 'suit-tab suit-tab-accountinvoice'
1475 dollar_amount = right_dollar_field("amount", "Amount")
1477 class PendingChargeInline(PlStackTabularInline):
1480 verbose_name_plural = "Charges"
1481 verbose_name = "Charge"
1482 exclude = ["invoice"]
1483 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1484 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1485 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1489 def queryset(self, request):
1490 qs = super(PendingChargeInline, self).queryset(request)
1491 qs = qs.filter(state="pending")
1494 dollar_amount = right_dollar_field("amount", "Amount")
1496 class PaymentInline(PlStackTabularInline):
1499 verbose_name_plural = "Payments"
1500 verbose_name = "Payment"
1501 fields = ["date", "dollar_amount"]
1502 readonly_fields = ["date", "dollar_amount"]
1503 suit_classes = 'suit-tab suit-tab-accountpayments'
1507 dollar_amount = right_dollar_field("amount", "Amount")
1509 class AccountAdmin(admin.ModelAdmin):
1510 list_display = ("site", "balance_due")
1512 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1515 (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1517 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1520 ('general','Account Details'),
1521 ('accountinvoice', 'Invoices'),
1522 ('accountpayments', 'Payments'),
1523 ('accountpendingcharges','Pending Charges'),
1526 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1527 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1528 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1530 # Now register the new UserAdmin...
1531 admin.site.register(User, UserAdmin)
1532 # ... and, since we're not using Django's builtin permissions,
1533 # unregister the Group model from admin.
1534 #admin.site.unregister(Group)
1536 #Do not show django evolution in the admin interface
1537 from django_evolution.models import Version, Evolution
1538 #admin.site.unregister(Version)
1539 #admin.site.unregister(Evolution)
1542 # When debugging it is often easier to see all the classes, but for regular use
1543 # only the top-levels should be displayed
1546 admin.site.register(Deployment, DeploymentAdmin)
1547 admin.site.register(Site, SiteAdmin)
1548 admin.site.register(Slice, SliceAdmin)
1549 admin.site.register(Service, ServiceAdmin)
1550 admin.site.register(Reservation, ReservationAdmin)
1551 admin.site.register(Network, NetworkAdmin)
1552 admin.site.register(Router, RouterAdmin)
1553 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1554 admin.site.register(Account, AccountAdmin)
1555 admin.site.register(Invoice, InvoiceAdmin)
1558 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1559 admin.site.register(ServiceClass, ServiceClassAdmin)
1560 #admin.site.register(PlanetStack)
1561 admin.site.register(Tag, TagAdmin)
1562 admin.site.register(DeploymentRole)
1563 admin.site.register(SiteRole)
1564 admin.site.register(SliceRole)
1565 admin.site.register(PlanetStackRole)
1566 admin.site.register(Node, NodeAdmin)
1567 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1568 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1569 admin.site.register(Sliver, SliverAdmin)
1570 admin.site.register(Image, ImageAdmin)
1571 admin.site.register(DashboardView, DashboardViewAdmin)
1572 admin.site.register(Flavor, FlavorAdmin)