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 or obj.backend_status.startswith("1 -"):
32 return '<span style="min-width:16px;"><img src="/static/admin/img/icon_success.gif"></span>'
34 if ((obj.backend_status is not None) and obj.backend_status.startswith("0 -")) or 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_id', 'instance_name', 'slice', 'deployment', 'flavor', 'image', 'node']
345 readonly_fields = ['backend_status_icon', 'all_ips_string', 'instance_id', '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 == 'deployment':
353 kwargs['queryset'] = Deployment.select_by_acl(request.user)
354 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_deployment_changed(this);"})
355 if 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', 'site_deployment']
395 readonly_fields = ('backend_status_icon', )
397 class DeploymentPrivilegeInline(PlStackTabularInline):
398 model = DeploymentPrivilege
400 suit_classes = 'suit-tab suit-tab-admin-only'
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 ControllerSiteInline(PlStackTabularInline):
408 model = ControllerSite
410 suit_classes = 'suit-tab suit-tab-admin-only'
411 fields = ['controller', 'site', 'tenant_id']
414 class SitePrivilegeInline(PlStackTabularInline):
415 model = SitePrivilege
417 suit_classes = 'suit-tab suit-tab-siteprivileges'
418 fields = ['backend_status_icon', 'user','site', 'role']
419 readonly_fields = ('backend_status_icon', )
421 def formfield_for_foreignkey(self, db_field, request, **kwargs):
422 if db_field.name == 'site':
423 kwargs['queryset'] = Site.select_by_user(request.user)
425 if db_field.name == 'user':
426 kwargs['queryset'] = User.select_by_user(request.user)
427 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
429 def queryset(self, request):
430 return SitePrivilege.select_by_user(request.user)
432 class SiteDeploymentInline(PlStackTabularInline):
433 model = SiteDeployment
435 suit_classes = 'suit-tab suit-tab-deployments'
436 fields = ['backend_status_icon', 'deployment','site', 'controller']
437 readonly_fields = ('backend_status_icon', )
439 def formfield_for_foreignkey(self, db_field, request, **kwargs):
440 if db_field.name == 'site':
441 kwargs['queryset'] = Site.select_by_user(request.user)
443 if db_field.name == 'deployment':
444 kwargs['queryset'] = Deployment.select_by_user(request.user)
446 if db_field.name == 'controller':
447 kwargs['queryset'] = Controller.select_by_user(request.user)
449 return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
451 def queryset(self, request):
452 return SiteDeployment.select_by_user(request.user)
455 class SlicePrivilegeInline(PlStackTabularInline):
456 model = SlicePrivilege
457 suit_classes = 'suit-tab suit-tab-sliceprivileges'
459 fields = ('backend_status_icon', 'user', 'slice', 'role')
460 readonly_fields = ('backend_status_icon', )
462 def formfield_for_foreignkey(self, db_field, request, **kwargs):
463 if db_field.name == 'slice':
464 kwargs['queryset'] = Slice.select_by_user(request.user)
465 if db_field.name == 'user':
466 kwargs['queryset'] = User.select_by_user(request.user)
468 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
470 def queryset(self, request):
471 return SlicePrivilege.select_by_user(request.user)
473 class SliceNetworkInline(PlStackTabularInline):
474 model = Network.slices.through
475 selflink_fieldname = "network"
477 verbose_name = "Network Connection"
478 verbose_name_plural = "Network Connections"
479 suit_classes = 'suit-tab suit-tab-slicenetworks'
480 fields = ['backend_status_icon', 'network']
481 readonly_fields = ('backend_status_icon', )
483 class ImageDeploymentsInline(PlStackTabularInline):
484 model = ImageDeployments
486 verbose_name = "Image Deployments"
487 verbose_name_plural = "Image Deployments"
488 suit_classes = 'suit-tab suit-tab-imagedeployments'
489 fields = ['backend_status_icon', 'image', 'deployment']
490 readonly_fields = ['backend_status_icon']
492 class ControllerImagesInline(PlStackTabularInline):
493 model = ControllerImages
495 verbose_name = "Controller Images"
496 verbose_name_plural = "Controller Images"
497 suit_classes = 'suit-tab suit-tab-admin-only'
498 fields = ['backend_status_icon', 'image', 'controller', 'glance_image_id']
499 readonly_fields = ['backend_status_icon', 'glance_image_id']
501 class SliceRoleAdmin(PlanetStackBaseAdmin):
505 class SiteRoleAdmin(PlanetStackBaseAdmin):
509 class DeploymentAdminForm(forms.ModelForm):
510 sites = forms.ModelMultipleChoiceField(
511 queryset=Site.objects.all(),
513 help_text="Select which sites are allowed to host nodes in this deployment",
514 widget=FilteredSelectMultiple(
515 verbose_name=('Sites'), is_stacked=False
518 images = forms.ModelMultipleChoiceField(
519 queryset=Image.objects.all(),
521 help_text="Select which images should be deployed on this deployment",
522 widget=FilteredSelectMultiple(
523 verbose_name=('Images'), is_stacked=False
526 flavors = forms.ModelMultipleChoiceField(
527 queryset=Flavor.objects.all(),
529 help_text="Select which flavors should be usable on this deployment",
530 widget=FilteredSelectMultiple(
531 verbose_name=('Flavors'), is_stacked=False
536 many_to_many = ["flavors",]
538 def __init__(self, *args, **kwargs):
539 request = kwargs.pop('request', None)
540 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
542 self.fields['accessControl'].initial = "allow site " + request.user.site.name
544 if self.instance and self.instance.pk:
545 self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments.all()]
546 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments.all()]
547 self.fields['flavors'].initial = self.instance.flavors.all()
549 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
550 """ helper function for handling m2m relations from the MultipleChoiceField
552 this_obj: the source object we want to link from
554 selected_objs: a list of destination objects we want to link to
556 all_relations: the full set of relations involving this_obj, including ones we don't want
558 relation_class: the class that implements the relation from source to dest
560 local_attrname: field name representing this_obj in relation_class
562 foreign_attrname: field name representing selected_objs in relation_class
564 This function will remove all newobjclass relations from this_obj
565 that are not contained in selected_objs, and add any relations that
566 are in selected_objs but don't exist in the data model yet.
569 existing_dest_objs = []
570 for relation in list(all_relations):
571 if getattr(relation, foreign_attrname) not in selected_objs:
572 #print "deleting site", sdp.site
575 existing_dest_objs.append(getattr(relation, foreign_attrname))
577 for dest_obj in selected_objs:
578 if dest_obj not in existing_dest_objs:
579 #print "adding site", site
580 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
581 relation = relation_class(**kwargs)
584 def save(self, commit=True):
585 deployment = super(DeploymentAdminForm, self).save(commit=False)
589 # this has to be done after save() if/when a deployment is first created
590 deployment.flavors = self.cleaned_data['flavors']
593 # save_m2m() doesn't seem to work with 'through' relations. So we
594 # create/destroy the through models ourselves. There has to be
597 self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments.all(), SiteDeployment, "deployment", "site")
598 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments.all(), ImageDeployments, "deployment", "image")
599 # manipulate_m2m_objs doesn't work for Flavor/Deployment relationship
600 # so well handle that manually here
601 for flavor in deployment.flavors.all():
602 if getattr(flavor, 'name') not in self.cleaned_data['flavors']:
603 deployment.flavors.remove(flavor)
604 for flavor in self.cleaned_data['flavors']:
605 if flavor not in deployment.flavors.all():
606 flavor.deployments.add(deployment)
612 class DeploymentAdminROForm(DeploymentAdminForm):
613 def save(self, commit=True):
614 raise PermissionDenied
616 class SiteAssocInline(PlStackTabularInline):
617 model = Site.deployments.through
619 suit_classes = 'suit-tab suit-tab-sites'
621 class DeploymentAdmin(PlanetStackBaseAdmin):
623 fieldList = ['backend_status_text', 'name', 'sites', 'images', 'flavors', 'accessControl']
624 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
625 # node no longer directly connected to deployment
626 #inlines = [DeploymentPrivilegeInline,NodeInline,TagInline,ImageDeploymentsInline]
627 inlines = [DeploymentPrivilegeInline,TagInline,ImageDeploymentsInline]
628 list_display = ['backend_status_icon', 'name']
629 list_display_links = ('backend_status_icon', 'name', )
630 readonly_fields = ('backend_status_text', )
632 user_readonly_fields = ['name']
634 # nodes no longer direclty connected to deployments
635 #suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags'),('imagedeployments','Images'))
636 suit_form_tabs =(('sites','Deployment Details'),('deploymentprivileges','Privileges'))
638 def get_form(self, request, obj=None, **kwargs):
639 if request.user.isReadOnlyUser():
640 kwargs["form"] = DeploymentAdminROForm
642 kwargs["form"] = DeploymentAdminForm
643 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
645 # from stackexchange: pass the request object into the form
647 class AdminFormMetaClass(adminForm):
648 def __new__(cls, *args, **kwargs):
649 kwargs['request'] = request
650 return adminForm(*args, **kwargs)
652 return AdminFormMetaClass
654 class ControllerAdminForm(forms.ModelForm):
655 sites = forms.ModelMultipleChoiceField(
656 queryset=Site.objects.all(),
658 help_text="Select which sites are managed by this controller",
659 widget=FilteredSelectMultiple(
660 verbose_name=('Sites'), is_stacked=False
667 def __init__(self, *args, **kwargs):
668 request = kwargs.pop('request', None)
669 super(ControllerAdminForm, self).__init__(*args, **kwargs)
671 if self.instance and self.instance.pk:
672 self.fields['sites'].initial = [x.site for x in self.instance.controllersite.all()]
674 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
675 """ helper function for handling m2m relations from the MultipleChoiceField
676 this_obj: the source object we want to link from
677 selected_objs: a list of destination objects we want to link to
678 all_relations: the full set of relations involving this_obj, including ones we don't want
679 relation_class: the class that implements the relation from source to dest
680 local_attrname: field name representing this_obj in relation_class
681 foreign_attrname: field name representing selected_objs in relation_class
682 This function will remove all newobjclass relations from this_obj
683 that are not contained in selected_objs, and add any relations that
684 are in selected_objs but don't exist in the data model yet.
686 existing_dest_objs = []
687 for relation in list(all_relations):
688 if getattr(relation, foreign_attrname) not in selected_objs:
689 #print "deleting site", sdp.site
692 existing_dest_objs.append(getattr(relation, foreign_attrname))
694 for dest_obj in selected_objs:
695 if dest_obj not in existing_dest_objs:
696 #print "adding site", site
697 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
698 relation = relation_class(**kwargs)
701 def save(self, commit=True):
702 controller = super(ControllerAdminForm, self).save(commit=False)
707 # save_m2m() doesn't seem to work with 'through' relations. So we
708 # create/destroy the through models ourselves. There has to be
710 self.manipulate_m2m_objs(controller, self.cleaned_data['sites'], controller.controllersite.all(), ControllerSite, "controller", "site")
717 class ControllerAdmin(PlanetStackBaseAdmin):
719 fieldList = ['name', 'backend_type', 'version', 'auth_url', 'admin_user', 'admin_tenant','admin_password']
720 #fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
721 inlines = [ControllerSiteInline] # ,ControllerImagesInline]
722 list_display = ['backend_status_icon', 'name', 'version', 'backend_type']
723 list_display_links = ('backend_status_icon', 'name', )
724 readonly_fields = ('backend_status_text',)
726 user_readonly_fields = []
728 def get_form(self, request, obj=None, **kwargs):
730 if request.user.isReadOnlyUser():
731 kwargs["form"] = ControllerAdminROForm
733 kwargs["form"] = ControllerAdminForm
734 adminForm = super(ControllerAdmin,self).get_form(request, obj, **kwargs)
736 # from stackexchange: pass the request object into the form
738 class AdminFormMetaClass(adminForm):
739 def __new__(cls, *args, **kwargs):
740 kwargs['request'] = request
741 return adminForm(*args, **kwargs)
743 return AdminFormMetaClass
745 def save_model(self, request, obj, form, change):
746 # update openstack connection to use this site/tenant
747 obj.save_by_user(request.user)
749 def delete_model(self, request, obj):
750 obj.delete_by_user(request.user)
752 class ServiceAttrAsTabInline(PlStackTabularInline):
753 model = ServiceAttribute
754 fields = ['name','value']
756 suit_classes = 'suit-tab suit-tab-serviceattrs'
758 class ServiceAdmin(PlanetStackBaseAdmin):
759 list_display = ("backend_status_icon","name","description","versionNumber","enabled","published")
760 list_display_links = ('backend_status_icon', 'name', )
761 fieldList = ["backend_status_text","name","description","versionNumber","enabled","published"]
762 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
763 inlines = [ServiceAttrAsTabInline,SliceInline]
764 readonly_fields = ('backend_status_text', )
766 user_readonly_fields = fieldList
768 suit_form_tabs =(('general', 'Service Details'),
770 ('serviceattrs','Additional Attributes'),
773 class SiteNodeInline(PlStackTabularInline):
775 fields = ['name', 'site_deployment']
777 suit_classes = 'suit-tab suit-tab-nodes'
779 class SiteAdmin(PlanetStackBaseAdmin):
780 #fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
781 fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'location']
783 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
784 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
786 #readonly_fields = ['backend_status_text', 'accountLink']
787 readonly_fields = ['backend_status_text']
789 #user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
790 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base']
792 list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
793 list_display_links = ('backend_status_icon', 'name', )
794 filter_horizontal = ('deployments',)
795 inlines = [SliceInline,UserInline,TagInline, SitePrivilegeInline, SiteDeploymentInline, SiteNodeInline]
796 admin_inlines = [ControllerSiteInline]
797 search_fields = ['name']
800 def suit_form_tabs(self):
801 tabs = [('general', 'Site Details'),
803 ('siteprivileges','Privileges'),
804 ('deployments','Deployments'),
809 request=getattr(_thread_locals, "request", None)
810 if request and request.user.is_admin:
811 tabs.append( ('admin-only', 'Admin-Only') )
815 def queryset(self, request):
816 return Site.select_by_user(request.user)
818 def get_formsets(self, request, obj=None):
819 for inline in self.get_inline_instances(request, obj):
820 # hide MyInline in the add view
823 if isinstance(inline, SliverInline):
824 inline.model.caller = request.user
825 yield inline.get_formset(request, obj)
827 def accountLink(self, obj):
828 link_obj = obj.accounts.all()
830 reverse_path = "admin:core_account_change"
831 url = reverse(reverse_path, args =(link_obj[0].id,))
832 return "<a href='%s'>%s</a>" % (url, "view billing details")
834 return "no billing data for this site"
835 accountLink.allow_tags = True
836 accountLink.short_description = "Billing"
838 def save_model(self, request, obj, form, change):
839 # update openstack connection to use this site/tenant
840 obj.save_by_user(request.user)
842 def delete_model(self, request, obj):
843 obj.delete_by_user(request.user)
846 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
847 fieldList = ['backend_status_text', 'user', 'site', 'role']
849 (None, {'fields': fieldList, 'classes':['collapse']})
851 readonly_fields = ('backend_status_text', )
852 list_display = ('backend_status_icon', 'user', 'site', 'role')
853 list_display_links = list_display
854 user_readonly_fields = fieldList
855 user_readonly_inlines = []
857 def formfield_for_foreignkey(self, db_field, request, **kwargs):
858 if db_field.name == 'site':
859 if not request.user.is_admin:
860 # only show sites where user is an admin or pi
862 for site_privilege in SitePrivilege.objects.filer(user=request.user):
863 if site_privilege.role.role_type in ['admin', 'pi']:
864 sites.add(site_privilege.site)
865 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
867 if db_field.name == 'user':
868 if not request.user.is_admin:
869 # only show users from sites where caller has admin or pi role
870 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
871 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
872 sites = [site_privilege.site for site_privilege in site_privileges]
873 site_privileges = SitePrivilege.objects.filter(site__in=sites)
874 emails = [site_privilege.user.email for site_privilege in site_privileges]
875 users = User.objects.filter(email__in=emails)
876 kwargs['queryset'] = users
878 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
880 def queryset(self, request):
881 # admins can see all privileges. Users can only see privileges at sites
882 # where they have the admin role or pi role.
883 qs = super(SitePrivilegeAdmin, self).queryset(request)
884 #if not request.user.is_admin:
885 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
886 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
887 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
888 # sites = Site.objects.filter(login_base__in=login_bases)
889 # qs = qs.filter(site__in=sites)
892 class SliceForm(forms.ModelForm):
896 'service': LinkedSelect
900 cleaned_data = super(SliceForm, self).clean()
901 name = cleaned_data.get('name')
902 site = cleaned_data.get('site')
903 slice_id = self.instance.id
904 if not site and slice_id:
905 site = Slice.objects.get(id=slice_id).site
906 if (not isinstance(site,Site)):
907 # previous code indicates 'site' could be a site_id and not a site?
908 site = Slice.objects.get(id=site.id)
909 if not name.startswith(site.login_base):
910 raise forms.ValidationError('slice name must begin with %s' % site.login_base)
913 class ControllerSliceInline(PlStackTabularInline):
914 model = ControllerSlice
916 verbose_name = "Controller Slices"
917 verbose_name_plural = "Controller Slices"
918 suit_classes = 'suit-tab suit-tab-admin-only'
919 fields = ['backend_status_icon', 'controller', 'tenant_id']
920 readonly_fields = ('backend_status_icon', 'controller' )
922 class SliceAdmin(PlanetStackBaseAdmin):
924 fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
925 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
926 readonly_fields = ('backend_status_text', )
927 list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_slivers')
928 list_display_links = ('backend_status_icon', 'name', )
929 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
930 admin_inlines = [ControllerSliceInline]
932 user_readonly_fields = fieldList
935 def suit_form_tabs(self):
936 tabs =[('general', 'Slice Details'),
937 ('slicenetworks','Networks'),
938 ('sliceprivileges','Privileges'),
939 ('slivers','Slivers'),
940 #('reservations','Reservations'),
944 request=getattr(_thread_locals, "request", None)
945 if request and request.user.is_admin:
946 tabs.append( ('admin-only', 'Admin-Only') )
950 def add_view(self, request, form_url='', extra_context=None):
951 # revert to default read-only fields
952 self.readonly_fields = ('backend_status_text',)
953 return super(SliceAdmin, self).add_view(request, form_url, extra_context=extra_context)
955 def change_view(self, request, object_id, form_url='', extra_context=None):
956 # cannot change the site of an existing slice so make the site field read only
958 self.readonly_fields = ('backend_status_text','site')
959 return super(SliceAdmin, self).change_view(request, object_id, form_url)
961 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
962 deployment_nodes = []
963 for node in Node.objects.all():
964 deployment_nodes.append( (node.site_deployment.deployment.id, node.id, node.name) )
966 deployment_flavors = []
967 for flavor in Flavor.objects.all():
968 for deployment in flavor.deployments.all():
969 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
971 deployment_images = []
972 for image in Image.objects.all():
973 for deployment_image in image.imagedeployments.all():
974 deployment_images.append( (deployment_image.deployment.id, image.id, image.name) )
976 site_login_bases = []
977 for site in Site.objects.all():
978 site_login_bases.append((site.id, site.login_base))
980 context["deployment_nodes"] = deployment_nodes
981 context["deployment_flavors"] = deployment_flavors
982 context["deployment_images"] = deployment_images
983 context["site_login_bases"] = site_login_bases
984 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
986 def formfield_for_foreignkey(self, db_field, request, **kwargs):
987 if db_field.name == 'site':
988 kwargs['queryset'] = Site.select_by_user(request.user)
989 kwargs['widget'] = forms.Select(attrs={'onChange': "update_slice_prefix(this, $($(this).closest('fieldset')[0]).find('.field-name input')[0].id)"})
991 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
993 def queryset(self, request):
994 # admins can see all keys. Users can only see slices they belong to.
995 return Slice.select_by_user(request.user)
997 def get_formsets(self, request, obj=None):
998 for inline in self.get_inline_instances(request, obj):
999 # hide MyInline in the add view
1002 if isinstance(inline, SliverInline):
1003 inline.model.caller = request.user
1004 yield inline.get_formset(request, obj)
1006 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
1008 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
1010 readonly_fields = ('backend_status_text', )
1011 list_display = ('backend_status_icon', 'user', 'slice', 'role')
1012 list_display_links = list_display
1014 user_readonly_fields = ['user', 'slice', 'role']
1015 user_readonly_inlines = []
1017 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1018 if db_field.name == 'slice':
1019 kwargs['queryset'] = Slice.select_by_user(request.user)
1021 if db_field.name == 'user':
1022 kwargs['queryset'] = User.select_by_user(request.user)
1024 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1026 def queryset(self, request):
1027 # admins can see all memberships. Users can only see memberships of
1028 # slices where they have the admin role.
1029 return SlicePrivilege.select_by_user(request.user)
1031 def save_model(self, request, obj, form, change):
1032 # update openstack connection to use this site/tenant
1033 auth = request.session.get('auth', {})
1034 auth['tenant'] = obj.slice.slicename
1035 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1038 def delete_model(self, request, obj):
1039 # update openstack connection to use this site/tenant
1040 auth = request.session.get('auth', {})
1041 auth['tenant'] = obj.slice.slicename
1042 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1046 class ImageAdmin(PlanetStackBaseAdmin):
1048 fieldsets = [('Image Details',
1049 {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
1050 'classes': ['suit-tab suit-tab-general']})
1052 readonly_fields = ('backend_status_text', )
1054 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'), ('controllerimages', 'Controllers'))
1056 inlines = [SliverInline, ControllerImagesInline]
1058 user_readonly_fields = ['name', 'disk_format', 'container_format']
1060 list_display = ['backend_status_icon', 'name']
1061 list_display_links = ('backend_status_icon', 'name', )
1063 class NodeForm(forms.ModelForm):
1066 'site': LinkedSelect,
1067 'deployment': LinkedSelect
1070 class NodeAdmin(PlanetStackBaseAdmin):
1072 list_display = ('backend_status_icon', 'name', 'site_deployment')
1073 list_display_links = ('backend_status_icon', 'name', )
1074 list_filter = ('site_deployment',)
1076 inlines = [TagInline,SliverInline]
1077 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site_deployment'], 'classes':['suit-tab suit-tab-details']})]
1078 readonly_fields = ('backend_status_text', )
1080 user_readonly_fields = ['name','site_deployment']
1081 user_readonly_inlines = [TagInline,SliverInline]
1083 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'))
1086 class SliverForm(forms.ModelForm):
1089 ip = forms.CharField(widget=PlainTextWidget)
1090 instance_name = forms.CharField(widget=PlainTextWidget)
1092 'ip': PlainTextWidget(),
1093 'instance_name': PlainTextWidget(),
1094 'instance_id': PlainTextWidget(),
1095 'slice': LinkedSelect,
1096 'deployment': LinkedSelect,
1097 'node': LinkedSelect,
1098 'image': LinkedSelect
1101 class TagAdmin(PlanetStackBaseAdmin):
1102 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
1103 list_display_links = list_display
1104 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
1105 user_readonly_inlines = []
1107 class SliverAdmin(PlanetStackBaseAdmin):
1110 ('Sliver Details', {'fields': ['backend_status_text', 'slice', 'deployment', 'node', 'ip', 'instance_id', 'instance_name', 'flavor', 'image', 'ssh_command'], 'classes': ['suit-tab suit-tab-general'], })
1112 readonly_fields = ('backend_status_text', 'ssh_command', )
1113 list_display = ['backend_status_icon', 'ip', 'instance_id', 'instance_name', 'slice', 'flavor', 'image', 'node', 'deployment']
1114 list_display_links = ('backend_status_icon', 'ip',)
1116 suit_form_tabs =(('general', 'Sliver Details'),)
1118 inlines = [TagInline]
1120 user_readonly_fields = ['slice', 'deployment', 'node', 'ip', 'instance_name', 'flavor', 'image']
1122 def ssh_command(self, obj):
1123 ssh_command = obj.get_ssh_command()
1127 return "(not available)"
1129 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1130 if db_field.name == 'slice':
1131 kwargs['queryset'] = Slice.select_by_user(request.user)
1133 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1135 def queryset(self, request):
1136 # admins can see all slivers. Users can only see slivers of
1137 # the slices they belong to.
1138 return Sliver.select_by_user(request.user)
1141 def get_formsets(self, request, obj=None):
1142 # make some fields read only if we are updating an existing record
1144 self.readonly_fields = ('backend_status_text', 'ssh_command', )
1146 self.readonly_fields = ('backend_status_text', 'ssh_command',)
1148 for inline in self.get_inline_instances(request, obj):
1149 # hide MyInline in the add view
1152 if isinstance(inline, SliverInline):
1153 inline.model.caller = request.user
1154 yield inline.get_formset(request, obj)
1156 #def save_model(self, request, obj, form, change):
1157 # # update openstack connection to use this site/tenant
1158 # auth = request.session.get('auth', {})
1159 # auth['tenant'] = obj.slice.name
1160 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1161 # obj.creator = request.user
1164 #def delete_model(self, request, obj):
1165 # # update openstack connection to use this site/tenant
1166 # auth = request.session.get('auth', {})
1167 # auth['tenant'] = obj.slice.name
1168 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1171 class UserCreationForm(forms.ModelForm):
1172 """A form for creating new users. Includes all the required
1173 fields, plus a repeated password."""
1174 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
1175 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
1179 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
1181 def clean_password2(self):
1182 # Check that the two password entries match
1183 password1 = self.cleaned_data.get("password1")
1184 password2 = self.cleaned_data.get("password2")
1185 if password1 and password2 and password1 != password2:
1186 raise forms.ValidationError("Passwords don't match")
1189 def save(self, commit=True):
1190 # Save the provided password in hashed format
1191 user = super(UserCreationForm, self).save(commit=False)
1192 user.password = self.cleaned_data["password1"]
1193 #user.set_password(self.cleaned_data["password1"])
1199 class UserChangeForm(forms.ModelForm):
1200 """A form for updating users. Includes all the fields on
1201 the user, but replaces the password field with admin's
1202 password hash display field.
1204 password = ReadOnlyPasswordHashField(label='Password',
1205 help_text= '<a href=\"password/\">Change Password</a>.')
1209 widgets = { 'public_key': UploadTextareaWidget, }
1211 def clean_password(self):
1212 # Regardless of what the user provides, return the initial value.
1213 # This is done here, rather than on the field, because the
1214 # field does not have access to the initial value
1215 return self.initial["password"]
1217 class UserDashboardViewInline(PlStackTabularInline):
1218 model = UserDashboardView
1220 suit_classes = 'suit-tab suit-tab-dashboards'
1221 fields = ['user', 'dashboardView', 'order']
1223 class ControllerUserInline(PlStackTabularInline):
1224 model = ControllerUser
1226 suit_classes = 'suit-tab suit-tab-admin-only'
1227 fields = ['controller', 'user', 'kuser_id']
1228 readonly_fields=['controller']
1231 class UserAdmin(PermissionCheckingAdminMixin, UserAdmin):
1232 # Note: Make sure PermissionCheckingAdminMixin is listed before
1233 # admin.ModelAdmin in the class declaration.
1238 # The forms to add and change user instances
1239 form = UserChangeForm
1240 add_form = UserCreationForm
1242 # The fields to be used in displaying the User model.
1243 # These override the definitions on the base UserAdmin
1244 # that reference specific fields on auth.User.
1245 list_display = ('backend_status_icon', 'email', 'firstname', 'lastname', 'site', 'last_login')
1246 list_display_links = ("email",)
1247 list_filter = ('site',)
1248 inlines = [SlicePrivilegeInline,SitePrivilegeInline,UserDashboardViewInline]
1249 admin_inlines = [ControllerUserInline]
1250 fieldListLoginDetails = ['backend_status_text', 'email','site','password','is_active','is_readonly','is_admin','public_key']
1251 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1254 ('Login Details', {'fields': ['backend_status_text', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1255 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1256 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1257 #('Important dates', {'fields': ('last_login',)}),
1261 'classes': ('wide',),
1262 'fields': ('site', 'email', 'firstname', 'lastname', 'is_admin', 'is_readonly', 'phone', 'public_key','password1', 'password2')},
1265 readonly_fields = ('backend_status_text', )
1266 search_fields = ('email',)
1267 ordering = ('email',)
1268 filter_horizontal = ()
1270 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1273 def suit_form_tabs(self):
1274 if getattr(_thread_locals, "obj", None) is None:
1277 tabs = [('general','Login Details'),
1278 ('contact','Contact Information'),
1279 ('sliceprivileges','Slice Privileges'),
1280 ('siteprivileges','Site Privileges'),
1281 ('dashboards','Dashboard Views')]
1283 request=getattr(_thread_locals, "request", None)
1284 if request and request.user.is_admin:
1285 tabs.append( ('admin-only', 'Admin-Only') )
1289 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1290 if db_field.name == 'site':
1291 kwargs['queryset'] = Site.select_by_user(request.user)
1293 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1295 def queryset(self, request):
1296 return User.select_by_user(request.user)
1298 def get_form(self, request, obj=None, **kwargs):
1299 # copy login details list
1300 login_details_fields = list(self.fieldListLoginDetails)
1301 if not request.user.is_admin:
1302 # only admins can see 'is_admin' and 'is_readonly' fields
1303 if 'is_admin' in login_details_fields:
1304 login_details_fields.remove('is_admin')
1305 if 'is_readonly' in login_details_fields:
1306 login_details_fields.remove('is_readonly')
1307 #if len(request.user.siteprivileges.filter(role__role = 'pi')) > 0:
1308 # only admins and pis can change a user's site
1309 # self.readonly_fields = ('backend_status_text', 'site')
1311 ('Login Details', {'fields': login_details_fields, 'classes':['suit-tab suit-tab-general']}),
1312 ('Contact Information', {'fields': self.fieldListContactInfo, 'classes':['suit-tab suit-tab-contact']}),
1314 return super(UserAdmin, self).get_form(request, obj, **kwargs)
1316 class ControllerDashboardViewInline(PlStackTabularInline):
1317 model = ControllerDashboardView
1319 fields = ["controller", "url"]
1320 suit_classes = 'suit-tab suit-tab-controllers'
1322 class DashboardViewAdmin(PlanetStackBaseAdmin):
1323 fieldsets = [('Dashboard View Details',
1324 {'fields': ['backend_status_text', 'name', 'url', 'enabled'],
1325 'classes': ['suit-tab suit-tab-general']})
1327 list_display = ["name", "enabled", "url"]
1328 readonly_fields = ('backend_status_text', )
1329 inlines = [ControllerDashboardViewInline]
1331 suit_form_tabs =(('general','Dashboard View Details'),
1332 ('controllers', 'Per-controller Dashboard Details'))
1334 class ServiceResourceInline(PlStackTabularInline):
1335 model = ServiceResource
1338 class ServiceClassAdmin(PlanetStackBaseAdmin):
1339 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1340 list_display_links = ('backend_status_icon', 'name', )
1341 inlines = [ServiceResourceInline]
1343 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1344 user_readonly_inlines = []
1346 class ReservedResourceInline(PlStackTabularInline):
1347 model = ReservedResource
1349 suit_classes = 'suit-tab suit-tab-reservedresources'
1351 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1352 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1354 if db_field.name == 'resource':
1355 # restrict resources to those that the slice's service class allows
1356 if request._slice is not None:
1357 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1358 if len(field.queryset) > 0:
1359 field.initial = field.queryset.all()[0]
1361 field.queryset = field.queryset.none()
\r
1362 elif db_field.name == 'sliver':
\r
1363 # restrict slivers to those that belong to the slice
\r
1364 if request._slice is not None:
\r
1365 field.queryset = field.queryset.filter(slice = request._slice)
1367 field.queryset = field.queryset.none()
\r
1371 def queryset(self, request):
1372 return ReservedResource.select_by_user(request.user)
1374 class ReservationChangeForm(forms.ModelForm):
1378 'slice' : LinkedSelect
1381 class ReservationAddForm(forms.ModelForm):
1382 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1383 refresh = forms.CharField(widget=forms.HiddenInput())
1386 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1388 def clean_slice(self):
1389 slice = self.cleaned_data.get("slice")
1390 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1392 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1398 'slice' : LinkedSelect
1402 class ReservationAddRefreshForm(ReservationAddForm):
1403 """ This form is displayed when the Reservation Form receives an update
1404 from the Slice dropdown onChange handler. It doesn't validate the
1405 data and doesn't save the data. This will cause the form to be
1409 """ don't validate anything other than slice """
1410 dont_validate_fields = ("startTime", "duration")
1412 def full_clean(self):
1413 result = super(ReservationAddForm, self).full_clean()
1415 for fieldname in self.dont_validate_fields:
1416 if fieldname in self._errors:
1417 del self._errors[fieldname]
1421 """ don't save anything """
1425 class ReservationAdmin(PlanetStackBaseAdmin):
1426 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
1427 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1428 readonly_fields = ('backend_status_text', )
1429 list_display = ('startTime', 'duration')
1430 form = ReservationAddForm
1432 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1434 inlines = [ReservedResourceInline]
1435 user_readonly_fields = fieldList
1437 def add_view(self, request, form_url='', extra_context=None):
1438 timezone.activate(request.user.timezone)
1439 request._refresh = False
1440 request._slice = None
1441 if request.method == 'POST':
1442 # "refresh" will be set to "1" if the form was submitted due to
1443 # a change in the Slice dropdown.
1444 if request.POST.get("refresh","1") == "1":
1445 request._refresh = True
1446 request.POST["refresh"] = "0"
1448 # Keep track of the slice that was selected, so the
1449 # reservedResource inline can filter items for the slice.
1450 request._slice = request.POST.get("slice",None)
1451 if (request._slice is not None):
1452 request._slice = Slice.objects.get(id=request._slice)
1454 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1457 def changelist_view(self, request, extra_context = None):
1458 timezone.activate(request.user.timezone)
1459 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1461 def get_form(self, request, obj=None, **kwargs):
1464 # For changes, set request._slice to the slice already set in the
1466 request._slice = obj.slice
1467 self.form = ReservationChangeForm
1469 if getattr(request, "_refresh", False):
1470 self.form = ReservationAddRefreshForm
1472 self.form = ReservationAddForm
1473 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1475 def get_readonly_fields(self, request, obj=None):
1476 if (obj is not None):
1477 # Prevent slice from being changed after the reservation has been
1483 def queryset(self, request):
1484 return Reservation.select_by_user(request.user)
1486 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1487 list_display = ("backend_status_icon", "name", )
1488 list_display_links = ('backend_status_icon', 'name', )
1489 user_readonly_fields = ['name']
1490 user_readonly_inlines = []
1492 class RouterAdmin(PlanetStackBaseAdmin):
1493 list_display = ("backend_status_icon", "name", )
1494 list_display_links = ('backend_status_icon', 'name', )
1495 user_readonly_fields = ['name']
1496 user_readonly_inlines = []
1498 class RouterInline(PlStackTabularInline):
1499 model = Router.networks.through
1501 verbose_name_plural = "Routers"
1502 verbose_name = "Router"
1503 suit_classes = 'suit-tab suit-tab-routers'
1505 class NetworkParameterInline(PlStackGenericTabularInline):
1506 model = NetworkParameter
1508 verbose_name_plural = "Parameters"
1509 verbose_name = "Parameter"
1510 suit_classes = 'suit-tab suit-tab-netparams'
1511 fields = ['backend_status_icon', 'parameter', 'value']
1512 readonly_fields = ('backend_status_icon', )
1514 class NetworkSliversInline(PlStackTabularInline):
1515 fields = ['backend_status_icon', 'network','sliver','ip']
1516 readonly_fields = ("backend_status_icon", "ip", )
1517 model = NetworkSliver
1518 selflink_fieldname = "sliver"
1520 verbose_name_plural = "Slivers"
1521 verbose_name = "Sliver"
1522 suit_classes = 'suit-tab suit-tab-networkslivers'
1524 class NetworkSlicesInline(PlStackTabularInline):
1525 model = NetworkSlice
1526 selflink_fieldname = "slice"
1528 verbose_name_plural = "Slices"
1529 verbose_name = "Slice"
1530 suit_classes = 'suit-tab suit-tab-networkslices'
1531 fields = ['backend_status_icon', 'network','slice']
1532 readonly_fields = ('backend_status_icon', )
1534 class ControllerNetworkInline(PlStackTabularInline):
1535 model = ControllerNetwork
1537 verbose_name_plural = "Controller Networks"
1538 verbose_name = "Controller Network"
1539 suit_classes = 'suit-tab suit-tab-admin-only'
1540 fields = ['backend_status_icon', 'controller','net_id','subnet_id']
1541 readonly_fields = ('backend_status_icon', )
1543 class NetworkForm(forms.ModelForm):
1547 'topologyParameters': UploadTextareaWidget,
1548 'controllerParameters': UploadTextareaWidget,
1551 class NetworkAdmin(PlanetStackBaseAdmin):
1552 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1553 list_display_links = ('backend_status_icon', 'name', )
1554 readonly_fields = ("subnet", )
1556 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1557 admin_inlines = [ControllerNetworkInline]
1562 (None, {'fields': ['backend_status_text', 'name','template','ports','labels','owner','guaranteed_bandwidth', 'permit_all_slices','permitted_slices','network_id','router_id','subnet_id','subnet'],
1563 'classes':['suit-tab suit-tab-general']}),
1564 (None, {'fields': ['topology_parameters', 'controller_url', 'controller_parameters'],
1565 'classes':['suit-tab suit-tab-sdn']}),
1568 readonly_fields = ('backend_status_text', )
1569 user_readonly_fields = ['name','template','ports','labels','owner','guaranteed_bandwidth', 'permit_all_slices','permitted_slices','network_id','router_id','subnet_id','subnet']
1572 def suit_form_tabs(self):
1573 tabs=[('general','Network Details'),
1574 ('sdn', 'SDN Configuration'),
1575 ('netparams', 'Parameters'),
1576 ('networkslivers','Slivers'),
1577 ('networkslices','Slices'),
1578 ('routers','Routers'),
1581 request=getattr(_thread_locals, "request", None)
1582 if request and request.user.is_admin:
1583 tabs.append( ('admin-only', 'Admin-Only') )
1588 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1589 list_display = ("backend_status_icon", "name", "guaranteed_bandwidth", "visibility")
1590 list_display_links = ('backend_status_icon', 'name', )
1591 user_readonly_fields = ["name", "guaranteed_bandwidth", "visibility"]
1592 user_readonly_inlines = []
1594 (None, {'fields': ['name', 'description', 'guaranteed_bandwidth', 'visibility', 'translation', 'shared_network_name', 'shared_network_id', 'topology_kind', 'controller_kind'],
1595 'classes':['suit-tab suit-tab-general']}),]
1596 suit_form_tabs = (('general','Network Template Details'), )
1598 class FlavorAdmin(PlanetStackBaseAdmin):
1599 list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1600 list_display_links = ("backend_status_icon", "name")
1601 user_readonly_fields = ("name", "flavor")
1602 fields = ("name", "description", "flavor", "order", "default")
1604 # register a signal that caches the user's credentials when they log in
1605 def cache_credentials(sender, user, request, **kwds):
1606 auth = {'username': request.POST['username'],
1607 'password': request.POST['password']}
1608 request.session['auth'] = auth
1609 user_logged_in.connect(cache_credentials)
1611 def dollar_field(fieldName, short_description):
1612 def newFunc(self, obj):
1614 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1616 x=getattr(obj, fieldName, 0.0)
1618 newFunc.short_description = short_description
1621 def right_dollar_field(fieldName, short_description):
1622 def newFunc(self, obj):
1624 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1625 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1627 x=getattr(obj, fieldName, 0.0)
1629 newFunc.short_description = short_description
1630 newFunc.allow_tags = True
1633 class InvoiceChargeInline(PlStackTabularInline):
1636 verbose_name_plural = "Charges"
1637 verbose_name = "Charge"
1638 exclude = ['account']
1639 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1640 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1644 dollar_amount = right_dollar_field("amount", "Amount")
1646 class InvoiceAdmin(admin.ModelAdmin):
1647 list_display = ("date", "account")
1649 inlines = [InvoiceChargeInline]
1651 fields = ["date", "account", "dollar_amount"]
1652 readonly_fields = ["date", "account", "dollar_amount"]
1654 dollar_amount = dollar_field("amount", "Amount")
1656 class InvoiceInline(PlStackTabularInline):
1659 verbose_name_plural = "Invoices"
1660 verbose_name = "Invoice"
1661 fields = ["date", "dollar_amount"]
1662 readonly_fields = ["date", "dollar_amount"]
1663 suit_classes = 'suit-tab suit-tab-accountinvoice'
1667 dollar_amount = right_dollar_field("amount", "Amount")
1669 class PendingChargeInline(PlStackTabularInline):
1672 verbose_name_plural = "Charges"
1673 verbose_name = "Charge"
1674 exclude = ["invoice"]
1675 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1676 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1677 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1681 def queryset(self, request):
1682 qs = super(PendingChargeInline, self).queryset(request)
1683 qs = qs.filter(state="pending")
1686 dollar_amount = right_dollar_field("amount", "Amount")
1688 class PaymentInline(PlStackTabularInline):
1691 verbose_name_plural = "Payments"
1692 verbose_name = "Payment"
1693 fields = ["date", "dollar_amount"]
1694 readonly_fields = ["date", "dollar_amount"]
1695 suit_classes = 'suit-tab suit-tab-accountpayments'
1699 dollar_amount = right_dollar_field("amount", "Amount")
1701 class AccountAdmin(admin.ModelAdmin):
1702 list_display = ("site", "balance_due")
1704 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1707 (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1709 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1712 ('general','Account Details'),
1713 ('accountinvoice', 'Invoices'),
1714 ('accountpayments', 'Payments'),
1715 ('accountpendingcharges','Pending Charges'),
1718 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1719 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1720 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1722 # Now register the new UserAdmin...
1723 admin.site.register(User, UserAdmin)
1724 # ... and, since we're not using Django's builtin permissions,
1725 # unregister the Group model from admin.
1726 #admin.site.unregister(Group)
1728 #Do not show django evolution in the admin interface
1729 from django_evolution.models import Version, Evolution
1730 #admin.site.unregister(Version)
1731 #admin.site.unregister(Evolution)
1734 # When debugging it is often easier to see all the classes, but for regular use
1735 # only the top-levels should be displayed
1738 admin.site.register(Deployment, DeploymentAdmin)
1739 admin.site.register(Controller, ControllerAdmin)
1740 admin.site.register(Site, SiteAdmin)
1741 admin.site.register(Slice, SliceAdmin)
1742 admin.site.register(Service, ServiceAdmin)
1743 #admin.site.register(Reservation, ReservationAdmin)
1744 admin.site.register(Network, NetworkAdmin)
1745 admin.site.register(Router, RouterAdmin)
1746 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1747 #admin.site.register(Account, AccountAdmin)
1748 #admin.site.register(Invoice, InvoiceAdmin)
1751 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1752 admin.site.register(ServiceClass, ServiceClassAdmin)
1753 #admin.site.register(PlanetStack)
1754 admin.site.register(Tag, TagAdmin)
1755 admin.site.register(ControllerRole)
1756 admin.site.register(SiteRole)
1757 admin.site.register(SliceRole)
1758 admin.site.register(PlanetStackRole)
1759 admin.site.register(Node, NodeAdmin)
1760 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1761 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1762 admin.site.register(Sliver, SliverAdmin)
1763 admin.site.register(Image, ImageAdmin)
1764 admin.site.register(DashboardView, DashboardViewAdmin)
1765 admin.site.register(Flavor, FlavorAdmin)