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', 'deployment', '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 == '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 ControllerPrivilegeInline(PlStackTabularInline):
408 model = ControllerPrivilege
410 suit_classes = 'suit-tab suit-tab-admin-only'
411 fields = ['backend_status_icon', 'user','role','controller']
412 readonly_fields = ('backend_status_icon', )
414 def queryset(self, request):
415 return ControllerPrivilege.select_by_user(request.user)
417 class SitePrivilegeInline(PlStackTabularInline):
418 model = SitePrivilege
420 suit_classes = 'suit-tab suit-tab-siteprivileges'
421 fields = ['backend_status_icon', 'user','site', 'role']
422 readonly_fields = ('backend_status_icon', )
424 def formfield_for_foreignkey(self, db_field, request, **kwargs):
425 if db_field.name == 'site':
426 kwargs['queryset'] = Site.select_by_user(request.user)
428 if db_field.name == 'user':
429 kwargs['queryset'] = User.select_by_user(request.user)
430 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
432 def queryset(self, request):
433 return SitePrivilege.select_by_user(request.user)
435 class SiteDeploymentsInline(PlStackTabularInline):
436 model = SiteDeployments
438 suit_classes = 'suit-tab suit-tab-deployments'
439 fields = ['backend_status_icon', 'deployment','site', 'controller']
440 readonly_fields = ('backend_status_icon', )
442 def formfield_for_foreignkey(self, db_field, request, **kwargs):
443 if db_field.name == 'site':
444 kwargs['queryset'] = Site.select_by_user(request.user)
446 if db_field.name == 'deployment':
447 kwargs['queryset'] = Deployment.select_by_user(request.user)
449 if db_field.name == 'controller':
450 kwargs['queryset'] = Controller.select_by_user(request.user)
452 return super(SiteDeploymentsInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
454 def queryset(self, request):
455 return SiteDeployments.select_by_user(request.user)
458 class SlicePrivilegeInline(PlStackTabularInline):
459 model = SlicePrivilege
460 suit_classes = 'suit-tab suit-tab-sliceprivileges'
462 fields = ('backend_status_icon', 'user', 'slice', 'role')
463 readonly_fields = ('backend_status_icon', )
465 def formfield_for_foreignkey(self, db_field, request, **kwargs):
466 if db_field.name == 'slice':
467 kwargs['queryset'] = Slice.select_by_user(request.user)
468 if db_field.name == 'user':
469 kwargs['queryset'] = User.select_by_user(request.user)
471 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
473 def queryset(self, request):
474 return SlicePrivilege.select_by_user(request.user)
476 class SliceNetworkInline(PlStackTabularInline):
477 model = Network.slices.through
478 selflink_fieldname = "network"
480 verbose_name = "Network Connection"
481 verbose_name_plural = "Network Connections"
482 suit_classes = 'suit-tab suit-tab-slicenetworks'
483 fields = ['backend_status_icon', 'network']
484 readonly_fields = ('backend_status_icon', )
486 class ImageDeploymentsInline(PlStackTabularInline):
487 model = ImageDeployments
489 verbose_name = "Image Deployments"
490 verbose_name_plural = "Image Deployments"
491 suit_classes = 'suit-tab suit-tab-imagedeployments'
492 fields = ['backend_status_icon', 'image', 'deployment']
493 readonly_fields = ['backend_status_icon']
495 class ControllerImagesInline(PlStackTabularInline):
496 model = ControllerImages
498 verbose_name = "Controller Images"
499 verbose_name_plural = "Controller Images"
500 suit_classes = 'suit-tab suit-tab-admin-only'
501 fields = ['backend_status_icon', 'image', 'controller', 'glance_image_id']
502 readonly_fields = ['backend_status_icon', 'glance_image_id']
504 class SliceRoleAdmin(PlanetStackBaseAdmin):
508 class SiteRoleAdmin(PlanetStackBaseAdmin):
512 class DeploymentAdminForm(forms.ModelForm):
513 sites = forms.ModelMultipleChoiceField(
514 queryset=Site.objects.all(),
516 help_text="Select which sites are allowed to host nodes in this deployment",
517 widget=FilteredSelectMultiple(
518 verbose_name=('Sites'), is_stacked=False
521 images = forms.ModelMultipleChoiceField(
522 queryset=Image.objects.all(),
524 help_text="Select which images should be deployed on this deployment",
525 widget=FilteredSelectMultiple(
526 verbose_name=('Images'), is_stacked=False
529 flavors = forms.ModelMultipleChoiceField(
530 queryset=Flavor.objects.all(),
532 help_text="Select which flavors should be usable on this deployment",
533 widget=FilteredSelectMultiple(
534 verbose_name=('Flavors'), is_stacked=False
539 many_to_many = ["flavors",]
541 def __init__(self, *args, **kwargs):
542 request = kwargs.pop('request', None)
543 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
545 self.fields['accessControl'].initial = "allow site " + request.user.site.name
547 if self.instance and self.instance.pk:
548 self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments.all()]
549 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments.all()]
550 self.fields['flavors'].initial = self.instance.flavors.all()
552 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
553 """ helper function for handling m2m relations from the MultipleChoiceField
555 this_obj: the source object we want to link from
557 selected_objs: a list of destination objects we want to link to
559 all_relations: the full set of relations involving this_obj, including ones we don't want
561 relation_class: the class that implements the relation from source to dest
563 local_attrname: field name representing this_obj in relation_class
565 foreign_attrname: field name representing selected_objs in relation_class
567 This function will remove all newobjclass relations from this_obj
568 that are not contained in selected_objs, and add any relations that
569 are in selected_objs but don't exist in the data model yet.
572 existing_dest_objs = []
573 for relation in list(all_relations):
574 if getattr(relation, foreign_attrname) not in selected_objs:
575 #print "deleting site", sdp.site
578 existing_dest_objs.append(getattr(relation, foreign_attrname))
580 for dest_obj in selected_objs:
581 if dest_obj not in existing_dest_objs:
582 #print "adding site", site
583 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
584 relation = relation_class(**kwargs)
587 def save(self, commit=True):
588 deployment = super(DeploymentAdminForm, self).save(commit=False)
592 # this has to be done after save() if/when a deployment is first created
593 deployment.flavors = self.cleaned_data['flavors']
596 # save_m2m() doesn't seem to work with 'through' relations. So we
597 # create/destroy the through models ourselves. There has to be
600 self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments.all(), SiteDeployments, "deployment", "site")
601 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments.all(), ImageDeployments, "deployment", "image")
602 # manipulate_m2m_objs doesn't work for Flavor/Deployment relationship
603 # so well handle that manually here
604 for flavor in deployment.flavors.all():
605 if getattr(flavor, 'name') not in self.cleaned_data['flavors']:
606 deployment.flavors.remove(flavor)
607 for flavor in self.cleaned_data['flavors']:
608 if flavor not in deployment.flavors.all():
609 flavor.deployments.add(deployment)
615 class DeploymentAdminROForm(DeploymentAdminForm):
616 def save(self, commit=True):
617 raise PermissionDenied
619 class SiteAssocInline(PlStackTabularInline):
620 model = Site.deployments.through
622 suit_classes = 'suit-tab suit-tab-sites'
624 class DeploymentAdmin(PlanetStackBaseAdmin):
626 fieldList = ['backend_status_text', 'name', 'sites', 'images', 'flavors', 'accessControl']
627 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
628 # node no longer directly connected to deployment
629 #inlines = [DeploymentPrivilegeInline,NodeInline,TagInline,ImageDeploymentsInline]
630 inlines = [DeploymentPrivilegeInline,TagInline,ImageDeploymentsInline]
631 list_display = ['backend_status_icon', 'name']
632 list_display_links = ('backend_status_icon', 'name', )
633 readonly_fields = ('backend_status_text', )
635 user_readonly_fields = ['name']
637 # nodes no longer direclty connected to deployments
638 #suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags'),('imagedeployments','Images'))
639 suit_form_tabs =(('sites','Deployment Details'),('deploymentprivileges','Privileges'),('tags','Tags'),('imagedeployments','Images'))
641 def get_form(self, request, obj=None, **kwargs):
642 if request.user.isReadOnlyUser():
643 kwargs["form"] = DeploymentAdminROForm
645 kwargs["form"] = DeploymentAdminForm
646 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
648 # from stackexchange: pass the request object into the form
650 class AdminFormMetaClass(adminForm):
651 def __new__(cls, *args, **kwargs):
652 kwargs['request'] = request
653 return adminForm(*args, **kwargs)
655 return AdminFormMetaClass
657 class ControllerAdminForm(forms.ModelForm):
658 site_deployments = forms.ModelMultipleChoiceField(
659 queryset=SiteDeployments.objects.all(),
661 help_text="Select which sites deployments are managed by this controller",
662 widget=FilteredSelectMultiple(
663 verbose_name=('Site Deployments'), is_stacked=False
670 def __init__(self, *args, **kwargs):
671 request = kwargs.pop('request', None)
672 super(ControllerAdminForm, self).__init__(*args, **kwargs)
674 if self.instance and self.instance.pk:
675 self.fields['site_deployments'].initial = [x.site_deployment for x in self.instance.controllersitedeployments.all()]
677 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
678 """ helper function for handling m2m relations from the MultipleChoiceField
679 this_obj: the source object we want to link from
680 selected_objs: a list of destination objects we want to link to
681 all_relations: the full set of relations involving this_obj, including ones we don't want
682 relation_class: the class that implements the relation from source to dest
683 local_attrname: field name representing this_obj in relation_class
684 foreign_attrname: field name representing selected_objs in relation_class
685 This function will remove all newobjclass relations from this_obj
686 that are not contained in selected_objs, and add any relations that
687 are in selected_objs but don't exist in the data model yet.
689 existing_dest_objs = []
690 for relation in list(all_relations):
691 if getattr(relation, foreign_attrname) not in selected_objs:
692 #print "deleting site", sdp.site
695 existing_dest_objs.append(getattr(relation, foreign_attrname))
697 for dest_obj in selected_objs:
698 if dest_obj not in existing_dest_objs:
699 #print "adding site", site
700 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
701 relation = relation_class(**kwargs)
704 def save(self, commit=True):
705 controller = super(ControllerAdminForm, self).save(commit=False)
710 # save_m2m() doesn't seem to work with 'through' relations. So we
711 # create/destroy the through models ourselves. There has to be
713 #self.manipulate_m2m_objs(controller, self.cleaned_data['site_deployments'], controller.controllersitedeployments.all(), ControllerSiteDeployments, "controller", "site_deployment")
720 class ControllerAdmin(PlanetStackBaseAdmin):
722 fieldList = ['name', 'version', 'backend_type', 'auth_url', 'admin_user', 'admin_tenant','admin_password']
723 #fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
724 inlines = [] # ,ControllerImagesInline]
725 list_display = ['backend_status_icon', 'name', 'version', 'backend_type']
726 list_display_links = ('backend_status_icon', 'name', )
727 readonly_fields = ('backend_status_text',)
729 user_readonly_fields = []
731 def get_form(self, request, obj=None, **kwargs):
733 if request.user.isReadOnlyUser():
734 kwargs["form"] = ControllerAdminROForm
736 kwargs["form"] = ControllerAdminForm
737 adminForm = super(ControllerAdmin,self).get_form(request, obj, **kwargs)
739 # from stackexchange: pass the request object into the form
741 class AdminFormMetaClass(adminForm):
742 def __new__(cls, *args, **kwargs):
743 kwargs['request'] = request
744 return adminForm(*args, **kwargs)
746 return AdminFormMetaClass
748 class ServiceAttrAsTabInline(PlStackTabularInline):
749 model = ServiceAttribute
750 fields = ['name','value']
752 suit_classes = 'suit-tab suit-tab-serviceattrs'
754 class ServiceAdmin(PlanetStackBaseAdmin):
755 list_display = ("backend_status_icon","name","description","versionNumber","enabled","published")
756 list_display_links = ('backend_status_icon', 'name', )
757 fieldList = ["backend_status_text","name","description","versionNumber","enabled","published"]
758 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
759 inlines = [ServiceAttrAsTabInline,SliceInline]
760 readonly_fields = ('backend_status_text', )
762 user_readonly_fields = fieldList
764 suit_form_tabs =(('general', 'Service Details'),
766 ('serviceattrs','Additional Attributes'),
769 class SiteAdmin(PlanetStackBaseAdmin):
770 fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
772 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
773 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
775 suit_form_tabs =(('general', 'Site Details'),
777 ('siteprivileges','Privileges'),
778 ('deployments','Deployments'),
783 readonly_fields = ['backend_status_text', 'accountLink']
785 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
787 list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
788 list_display_links = ('backend_status_icon', 'name', )
789 filter_horizontal = ('deployments',)
790 inlines = [SliceInline,UserInline,TagInline, SitePrivilegeInline, SiteDeploymentsInline]
791 search_fields = ['name']
793 def queryset(self, request):
794 return Site.select_by_user(request.user)
796 def get_formsets(self, request, obj=None):
797 for inline in self.get_inline_instances(request, obj):
798 # hide MyInline in the add view
801 if isinstance(inline, SliverInline):
802 inline.model.caller = request.user
803 yield inline.get_formset(request, obj)
805 def accountLink(self, obj):
806 link_obj = obj.accounts.all()
808 reverse_path = "admin:core_account_change"
809 url = reverse(reverse_path, args =(link_obj[0].id,))
810 return "<a href='%s'>%s</a>" % (url, "view billing details")
812 return "no billing data for this site"
813 accountLink.allow_tags = True
814 accountLink.short_description = "Billing"
816 def save_model(self, request, obj, form, change):
817 # update openstack connection to use this site/tenant
818 obj.save_by_user(request.user)
820 def delete_model(self, request, obj):
821 obj.delete_by_user(request.user)
824 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
825 fieldList = ['backend_status_text', 'user', 'site', 'role']
827 (None, {'fields': fieldList, 'classes':['collapse']})
829 readonly_fields = ('backend_status_text', )
830 list_display = ('backend_status_icon', 'user', 'site', 'role')
831 list_display_links = list_display
832 user_readonly_fields = fieldList
833 user_readonly_inlines = []
835 def formfield_for_foreignkey(self, db_field, request, **kwargs):
836 if db_field.name == 'site':
837 if not request.user.is_admin:
838 # only show sites where user is an admin or pi
840 for site_privilege in SitePrivilege.objects.filer(user=request.user):
841 if site_privilege.role.role_type in ['admin', 'pi']:
842 sites.add(site_privilege.site)
843 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
845 if db_field.name == 'user':
846 if not request.user.is_admin:
847 # only show users from sites where caller has admin or pi role
848 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
849 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
850 sites = [site_privilege.site for site_privilege in site_privileges]
851 site_privileges = SitePrivilege.objects.filter(site__in=sites)
852 emails = [site_privilege.user.email for site_privilege in site_privileges]
853 users = User.objects.filter(email__in=emails)
854 kwargs['queryset'] = users
856 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
858 def queryset(self, request):
859 # admins can see all privileges. Users can only see privileges at sites
860 # where they have the admin role or pi role.
861 qs = super(SitePrivilegeAdmin, self).queryset(request)
862 #if not request.user.is_admin:
863 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
864 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
865 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
866 # sites = Site.objects.filter(login_base__in=login_bases)
867 # qs = qs.filter(site__in=sites)
870 class SliceForm(forms.ModelForm):
874 'service': LinkedSelect
878 cleaned_data = super(SliceForm, self).clean()
879 name = cleaned_data.get('name')
880 site = cleaned_data.get('site')
881 slice_id = self.instance.id
882 if not site and slice_id:
883 site = Slice.objects.get(id=slice_id).site
884 if (not isinstance(site,Site)):
885 # previous code indicates 'site' could be a site_id and not a site?
886 site = Slice.objects.get(id=site.id)
887 if not name.startswith(site.login_base):
888 raise forms.ValidationError('slice name must begin with %s' % site.login_base)
891 class ControllerSlicesInline(PlStackTabularInline):
892 model = ControllerSlices
894 verbose_name = "Controller Slices"
895 verbose_name_plural = "Controller Slices"
896 suit_classes = 'suit-tab suit-tab-admin-only'
897 fields = ['backend_status_icon', 'controller', 'tenant_id']
898 readonly_fields = ('backend_status_icon', )
900 class SliceAdmin(PlanetStackBaseAdmin):
902 fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
903 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
904 readonly_fields = ('backend_status_text', )
905 list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_slivers')
906 list_display_links = ('backend_status_icon', 'name', )
907 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
908 admin_inlines = [ControllerSlicesInline]
910 user_readonly_fields = fieldList
913 def suit_form_tabs(self):
914 tabs =[('general', 'Slice Details'),
915 ('slicenetworks','Networks'),
916 ('sliceprivileges','Privileges'),
917 ('slivers','Slivers'),
919 ('reservations','Reservations'),
922 request=getattr(_thread_locals, "request", None)
923 if request and request.user.is_admin:
924 tabs.append( ('admin-only', 'Admin-Only') )
928 def add_view(self, request, form_url='', extra_context=None):
929 # revert to default read-only fields
930 self.readonly_fields = ('backend_status_text',)
931 return super(SliceAdmin, self).add_view(request, form_url, extra_context=extra_context)
933 def change_view(self, request, object_id, form_url='', extra_context=None):
934 # cannot change the site of an existing slice so make the site field read only
936 self.readonly_fields = ('backend_status_text','site')
937 return super(SliceAdmin, self).change_view(request, object_id, form_url)
939 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
940 deployment_nodes = []
941 for node in Node.objects.all():
942 deployment_nodes.append( (node.site_deployment.id, node.id, node.name) )
944 deployment_flavors = []
945 for flavor in Flavor.objects.all():
946 for deployment in flavor.deployments.all():
947 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
949 deployment_images = []
950 for image in Image.objects.all():
951 for deployment_image in image.imagedeployments.all():
952 deployment_images.append( (deployment_image.deployment.id, image.id, image.name) )
954 site_login_bases = []
955 for site in Site.objects.all():
956 site_login_bases.append((site.id, site.login_base))
958 context["deployment_nodes"] = deployment_nodes
959 context["deployment_flavors"] = deployment_flavors
960 context["deployment_images"] = deployment_images
961 context["site_login_bases"] = site_login_bases
962 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
964 def formfield_for_foreignkey(self, db_field, request, **kwargs):
965 if db_field.name == 'site':
966 kwargs['queryset'] = Site.select_by_user(request.user)
967 kwargs['widget'] = forms.Select(attrs={'onChange': "update_slice_prefix(this, $($(this).closest('fieldset')[0]).find('.field-name input')[0].id)"})
969 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
971 def queryset(self, request):
972 # admins can see all keys. Users can only see slices they belong to.
973 return Slice.select_by_user(request.user)
975 def get_formsets(self, request, obj=None):
976 for inline in self.get_inline_instances(request, obj):
977 # hide MyInline in the add view
980 if isinstance(inline, SliverInline):
981 inline.model.caller = request.user
982 yield inline.get_formset(request, obj)
984 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
986 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
988 readonly_fields = ('backend_status_text', )
989 list_display = ('backend_status_icon', 'user', 'slice', 'role')
990 list_display_links = list_display
992 user_readonly_fields = ['user', 'slice', 'role']
993 user_readonly_inlines = []
995 def formfield_for_foreignkey(self, db_field, request, **kwargs):
996 if db_field.name == 'slice':
997 kwargs['queryset'] = Slice.select_by_user(request.user)
999 if db_field.name == 'user':
1000 kwargs['queryset'] = User.select_by_user(request.user)
1002 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1004 def queryset(self, request):
1005 # admins can see all memberships. Users can only see memberships of
1006 # slices where they have the admin role.
1007 return SlicePrivilege.select_by_user(request.user)
1009 def save_model(self, request, obj, form, change):
1010 # update openstack connection to use this site/tenant
1011 auth = request.session.get('auth', {})
1012 auth['tenant'] = obj.slice.slicename
1013 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1016 def delete_model(self, request, obj):
1017 # update openstack connection to use this site/tenant
1018 auth = request.session.get('auth', {})
1019 auth['tenant'] = obj.slice.slicename
1020 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1024 class ImageAdmin(PlanetStackBaseAdmin):
1026 fieldsets = [('Image Details',
1027 {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
1028 'classes': ['suit-tab suit-tab-general']})
1030 readonly_fields = ('backend_status_text', )
1032 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'), ('controllerimages', 'Controllers'))
1034 inlines = [SliverInline, ControllerImagesInline]
1036 user_readonly_fields = ['name', 'disk_format', 'container_format']
1038 list_display = ['backend_status_icon', 'name']
1039 list_display_links = ('backend_status_icon', 'name', )
1041 class NodeForm(forms.ModelForm):
1044 'site': LinkedSelect,
1045 'deployment': LinkedSelect
1048 class NodeAdmin(PlanetStackBaseAdmin):
1050 list_display = ('backend_status_icon', 'name', 'site_deployment')
1051 list_display_links = ('backend_status_icon', 'name', )
1052 list_filter = ('site_deployment',)
1054 inlines = [TagInline,SliverInline]
1055 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site_deployment'], 'classes':['suit-tab suit-tab-details']})]
1056 readonly_fields = ('backend_status_text', )
1058 user_readonly_fields = ['name','site_deployment']
1059 user_readonly_inlines = [TagInline,SliverInline]
1061 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
1064 class SliverForm(forms.ModelForm):
1067 ip = forms.CharField(widget=PlainTextWidget)
1068 instance_name = forms.CharField(widget=PlainTextWidget)
1070 'ip': PlainTextWidget(),
1071 'instance_name': PlainTextWidget(),
1072 'slice': LinkedSelect,
1073 'deployment': LinkedSelect,
1074 'node': LinkedSelect,
1075 'image': LinkedSelect
1078 class TagAdmin(PlanetStackBaseAdmin):
1079 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
1080 list_display_links = list_display
1081 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
1082 user_readonly_inlines = []
1084 class SliverAdmin(PlanetStackBaseAdmin):
1087 ('Sliver Details', {'fields': ['backend_status_text', 'slice', 'deployment', 'node', 'ip', 'instance_name', 'flavor', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
1089 readonly_fields = ('backend_status_text', )
1090 list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'flavor', 'image', 'node', 'deployment']
1091 list_display_links = ('backend_status_icon', 'ip',)
1093 suit_form_tabs =(('general', 'Sliver Details'),
1097 inlines = [TagInline]
1099 user_readonly_fields = ['slice', 'deployment', 'node', 'ip', 'instance_name', 'flavor', 'image']
1101 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1102 if db_field.name == 'slice':
1103 kwargs['queryset'] = Slice.select_by_user(request.user)
1105 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1107 def queryset(self, request):
1108 # admins can see all slivers. Users can only see slivers of
1109 # the slices they belong to.
1110 return Sliver.select_by_user(request.user)
1113 def get_formsets(self, request, obj=None):
1114 # make some fields read only if we are updating an existing record
1116 #self.readonly_fields = ('ip', 'instance_name')
1117 self.readonly_fields = ('backend_status_text',)
1119 self.readonly_fields = ('backend_status_text',)
1120 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
1122 for inline in self.get_inline_instances(request, obj):
1123 # hide MyInline in the add view
1126 if isinstance(inline, SliverInline):
1127 inline.model.caller = request.user
1128 yield inline.get_formset(request, obj)
1130 #def save_model(self, request, obj, form, change):
1131 # # update openstack connection to use this site/tenant
1132 # auth = request.session.get('auth', {})
1133 # auth['tenant'] = obj.slice.name
1134 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1135 # obj.creator = request.user
1138 #def delete_model(self, request, obj):
1139 # # update openstack connection to use this site/tenant
1140 # auth = request.session.get('auth', {})
1141 # auth['tenant'] = obj.slice.name
1142 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1145 class UserCreationForm(forms.ModelForm):
1146 """A form for creating new users. Includes all the required
1147 fields, plus a repeated password."""
1148 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
1149 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
1153 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
1155 def clean_password2(self):
1156 # Check that the two password entries match
1157 password1 = self.cleaned_data.get("password1")
1158 password2 = self.cleaned_data.get("password2")
1159 if password1 and password2 and password1 != password2:
1160 raise forms.ValidationError("Passwords don't match")
1163 def save(self, commit=True):
1164 # Save the provided password in hashed format
1165 user = super(UserCreationForm, self).save(commit=False)
1166 user.password = self.cleaned_data["password1"]
1167 #user.set_password(self.cleaned_data["password1"])
1173 class UserChangeForm(forms.ModelForm):
1174 """A form for updating users. Includes all the fields on
1175 the user, but replaces the password field with admin's
1176 password hash display field.
1178 password = ReadOnlyPasswordHashField(label='Password',
1179 help_text= '<a href=\"password/\">Change Password</a>.')
1183 widgets = { 'public_key': UploadTextareaWidget, }
1185 def clean_password(self):
1186 # Regardless of what the user provides, return the initial value.
1187 # This is done here, rather than on the field, because the
1188 # field does not have access to the initial value
1189 return self.initial["password"]
1191 class UserDashboardViewInline(PlStackTabularInline):
1192 model = UserDashboardView
1194 suit_classes = 'suit-tab suit-tab-dashboards'
1195 fields = ['user', 'dashboardView', 'order']
1197 class UserAdmin(PermissionCheckingAdminMixin, UserAdmin):
1198 # Note: Make sure PermissionCheckingAdminMixin is listed before
1199 # admin.ModelAdmin in the class declaration.
1204 # The forms to add and change user instances
1205 form = UserChangeForm
1206 add_form = UserCreationForm
1208 # The fields to be used in displaying the User model.
1209 # These override the definitions on the base UserAdmin
1210 # that reference specific fields on auth.User.
1211 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
1212 list_filter = ('site',)
1213 inlines = [SlicePrivilegeInline,SitePrivilegeInline,ControllerPrivilegeInline,UserDashboardViewInline]
1215 fieldListLoginDetails = ['backend_status_text', 'email','site','password','is_active','is_readonly','is_admin','public_key']
1216 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1219 ('Login Details', {'fields': ['backend_status_text', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1220 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1221 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1222 #('Important dates', {'fields': ('last_login',)}),
1226 'classes': ('wide',),
1227 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')},
1230 readonly_fields = ('backend_status_text', )
1231 search_fields = ('email',)
1232 ordering = ('email',)
1233 filter_horizontal = ()
1235 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1238 def suit_form_tabs(self):
1239 if getattr(_thread_locals, "obj", None) is None:
1242 return (('general','Login Details'),
1243 ('contact','Contact Information'),
1244 ('sliceprivileges','Slice Privileges'),
1245 ('siteprivileges','Site Privileges'),
1246 ('controllerprivileges','Controller Privileges'),
1247 ('dashboards','Dashboard Views'))
1249 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1250 if db_field.name == 'site':
1251 kwargs['queryset'] = Site.select_by_user(request.user)
1253 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1255 def queryset(self, request):
1256 return User.select_by_user(request.user)
1258 class ControllerDashboardViewInline(PlStackTabularInline):
1259 model = ControllerDashboardView
1261 fields = ["controller", "url"]
1262 suit_classes = 'suit-tab suit-tab-controllers'
1264 class DashboardViewAdmin(PlanetStackBaseAdmin):
1265 fieldsets = [('Dashboard View Details',
1266 {'fields': ['backend_status_text', 'name', 'url'],
1267 'classes': ['suit-tab suit-tab-general']})
1269 readonly_fields = ('backend_status_text', )
1270 inlines = [ControllerDashboardViewInline]
1272 suit_form_tabs =(('general','Dashboard View Details'),
1273 ('controllers', 'Per-controller Dashboard Details'))
1275 class ServiceResourceInline(PlStackTabularInline):
1276 model = ServiceResource
1279 class ServiceClassAdmin(PlanetStackBaseAdmin):
1280 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1281 list_display_links = ('backend_status_icon', 'name', )
1282 inlines = [ServiceResourceInline]
1284 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1285 user_readonly_inlines = []
1287 class ReservedResourceInline(PlStackTabularInline):
1288 model = ReservedResource
1290 suit_classes = 'suit-tab suit-tab-reservedresources'
1292 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1293 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1295 if db_field.name == 'resource':
1296 # restrict resources to those that the slice's service class allows
1297 if request._slice is not None:
1298 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1299 if len(field.queryset) > 0:
1300 field.initial = field.queryset.all()[0]
1302 field.queryset = field.queryset.none()
\r
1303 elif db_field.name == 'sliver':
\r
1304 # restrict slivers to those that belong to the slice
\r
1305 if request._slice is not None:
\r
1306 field.queryset = field.queryset.filter(slice = request._slice)
1308 field.queryset = field.queryset.none()
\r
1312 def queryset(self, request):
1313 return ReservedResource.select_by_user(request.user)
1315 class ReservationChangeForm(forms.ModelForm):
1319 'slice' : LinkedSelect
1322 class ReservationAddForm(forms.ModelForm):
1323 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1324 refresh = forms.CharField(widget=forms.HiddenInput())
1327 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1329 def clean_slice(self):
1330 slice = self.cleaned_data.get("slice")
1331 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1333 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1339 'slice' : LinkedSelect
1343 class ReservationAddRefreshForm(ReservationAddForm):
1344 """ This form is displayed when the Reservation Form receives an update
1345 from the Slice dropdown onChange handler. It doesn't validate the
1346 data and doesn't save the data. This will cause the form to be
1350 """ don't validate anything other than slice """
1351 dont_validate_fields = ("startTime", "duration")
1353 def full_clean(self):
1354 result = super(ReservationAddForm, self).full_clean()
1356 for fieldname in self.dont_validate_fields:
1357 if fieldname in self._errors:
1358 del self._errors[fieldname]
1362 """ don't save anything """
1366 class ReservationAdmin(PlanetStackBaseAdmin):
1367 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
1368 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1369 readonly_fields = ('backend_status_text', )
1370 list_display = ('startTime', 'duration')
1371 form = ReservationAddForm
1373 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1375 inlines = [ReservedResourceInline]
1376 user_readonly_fields = fieldList
1378 def add_view(self, request, form_url='', extra_context=None):
1379 timezone.activate(request.user.timezone)
1380 request._refresh = False
1381 request._slice = None
1382 if request.method == 'POST':
1383 # "refresh" will be set to "1" if the form was submitted due to
1384 # a change in the Slice dropdown.
1385 if request.POST.get("refresh","1") == "1":
1386 request._refresh = True
1387 request.POST["refresh"] = "0"
1389 # Keep track of the slice that was selected, so the
1390 # reservedResource inline can filter items for the slice.
1391 request._slice = request.POST.get("slice",None)
1392 if (request._slice is not None):
1393 request._slice = Slice.objects.get(id=request._slice)
1395 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1398 def changelist_view(self, request, extra_context = None):
1399 timezone.activate(request.user.timezone)
1400 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1402 def get_form(self, request, obj=None, **kwargs):
1405 # For changes, set request._slice to the slice already set in the
1407 request._slice = obj.slice
1408 self.form = ReservationChangeForm
1410 if getattr(request, "_refresh", False):
1411 self.form = ReservationAddRefreshForm
1413 self.form = ReservationAddForm
1414 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1416 def get_readonly_fields(self, request, obj=None):
1417 if (obj is not None):
1418 # Prevent slice from being changed after the reservation has been
1424 def queryset(self, request):
1425 return Reservation.select_by_user(request.user)
1427 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1428 list_display = ("backend_status_icon", "name", )
1429 list_display_links = ('backend_status_icon', 'name', )
1430 user_readonly_fields = ['name']
1431 user_readonly_inlines = []
1433 class RouterAdmin(PlanetStackBaseAdmin):
1434 list_display = ("backend_status_icon", "name", )
1435 list_display_links = ('backend_status_icon', 'name', )
1436 user_readonly_fields = ['name']
1437 user_readonly_inlines = []
1439 class RouterInline(PlStackTabularInline):
1440 model = Router.networks.through
1442 verbose_name_plural = "Routers"
1443 verbose_name = "Router"
1444 suit_classes = 'suit-tab suit-tab-routers'
1446 class NetworkParameterInline(PlStackGenericTabularInline):
1447 model = NetworkParameter
1449 verbose_name_plural = "Parameters"
1450 verbose_name = "Parameter"
1451 suit_classes = 'suit-tab suit-tab-netparams'
1452 fields = ['backend_status_icon', 'parameter', 'value']
1453 readonly_fields = ('backend_status_icon', )
1455 class NetworkSliversInline(PlStackTabularInline):
1456 fields = ['backend_status_icon', 'network','sliver','ip']
1457 readonly_fields = ("backend_status_icon", "ip", )
1458 model = NetworkSliver
1459 selflink_fieldname = "sliver"
1461 verbose_name_plural = "Slivers"
1462 verbose_name = "Sliver"
1463 suit_classes = 'suit-tab suit-tab-networkslivers'
1465 class NetworkSlicesInline(PlStackTabularInline):
1466 model = NetworkSlice
1467 selflink_fieldname = "slice"
1469 verbose_name_plural = "Slices"
1470 verbose_name = "Slice"
1471 suit_classes = 'suit-tab suit-tab-networkslices'
1472 fields = ['backend_status_icon', 'network','slice']
1473 readonly_fields = ('backend_status_icon', )
1475 class ControllerNetworksInline(PlStackTabularInline):
1476 model = ControllerNetworks
1478 verbose_name_plural = "Controller Networks"
1479 verbose_name = "Controller Network"
1480 suit_classes = 'suit-tab suit-tab-admin-only'
1481 fields = ['backend_status_icon', 'controller','net_id','subnet_id']
1482 readonly_fields = ('backend_status_icon', )
1484 class NetworkForm(forms.ModelForm):
1488 'topologyParameters': UploadTextareaWidget,
1489 'controllerParameters': UploadTextareaWidget,
1492 class NetworkAdmin(PlanetStackBaseAdmin):
1493 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1494 list_display_links = ('backend_status_icon', 'name', )
1495 readonly_fields = ("subnet", )
1497 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1498 admin_inlines = [ControllerNetworksInline]
1503 (None, {'fields': ['backend_status_text', 'name','template','ports','labels','owner','guaranteed_bandwidth', 'permit_all_slices','permitted_slices','network_id','router_id','subnet_id','subnet'],
1504 'classes':['suit-tab suit-tab-general']}),
1505 (None, {'fields': ['topology_parameters', 'controller_url', 'controller_parameters'],
1506 'classes':['suit-tab suit-tab-sdn']}),
1509 readonly_fields = ('backend_status_text', )
1510 user_readonly_fields = ['name','template','ports','labels','owner','guaranteed_bandwidth', 'permit_all_slices','permitted_slices','network_id','router_id','subnet_id','subnet']
1513 def suit_form_tabs(self):
1514 tabs=[('general','Network Details'),
1515 ('sdn', 'SDN Configuration'),
1516 ('netparams', 'Parameters'),
1517 ('networkslivers','Slivers'),
1518 ('networkslices','Slices'),
1519 ('routers','Routers'),
1522 request=getattr(_thread_locals, "request", None)
1523 if request and request.user.is_admin:
1524 tabs.append( ('admin-only', 'Admin-Only') )
1529 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1530 list_display = ("backend_status_icon", "name", "guaranteed_bandwidth", "visibility")
1531 list_display_links = ('backend_status_icon', 'name', )
1532 user_readonly_fields = ["name", "guaranteed_bandwidth", "visibility"]
1533 user_readonly_inlines = []
1535 (None, {'fields': ['name', 'description', 'guaranteed_bandwidth', 'visibility', 'translation', 'shared_network_name', 'shared_network_id', 'topology_kind', 'controller_kind'],
1536 'classes':['suit-tab suit-tab-general']}),]
1537 suit_form_tabs = (('general','Network Template Details'), )
1539 class FlavorAdmin(PlanetStackBaseAdmin):
1540 list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1541 list_display_links = ("backend_status_icon", "name")
1542 user_readonly_fields = ("name", "flavor")
1543 fields = ("name", "description", "flavor", "order", "default")
1545 # register a signal that caches the user's credentials when they log in
1546 def cache_credentials(sender, user, request, **kwds):
1547 auth = {'username': request.POST['username'],
1548 'password': request.POST['password']}
1549 request.session['auth'] = auth
1550 user_logged_in.connect(cache_credentials)
1552 def dollar_field(fieldName, short_description):
1553 def newFunc(self, obj):
1555 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1557 x=getattr(obj, fieldName, 0.0)
1559 newFunc.short_description = short_description
1562 def right_dollar_field(fieldName, short_description):
1563 def newFunc(self, obj):
1565 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1566 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1568 x=getattr(obj, fieldName, 0.0)
1570 newFunc.short_description = short_description
1571 newFunc.allow_tags = True
1574 class InvoiceChargeInline(PlStackTabularInline):
1577 verbose_name_plural = "Charges"
1578 verbose_name = "Charge"
1579 exclude = ['account']
1580 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1581 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1585 dollar_amount = right_dollar_field("amount", "Amount")
1587 class InvoiceAdmin(admin.ModelAdmin):
1588 list_display = ("date", "account")
1590 inlines = [InvoiceChargeInline]
1592 fields = ["date", "account", "dollar_amount"]
1593 readonly_fields = ["date", "account", "dollar_amount"]
1595 dollar_amount = dollar_field("amount", "Amount")
1597 class InvoiceInline(PlStackTabularInline):
1600 verbose_name_plural = "Invoices"
1601 verbose_name = "Invoice"
1602 fields = ["date", "dollar_amount"]
1603 readonly_fields = ["date", "dollar_amount"]
1604 suit_classes = 'suit-tab suit-tab-accountinvoice'
1608 dollar_amount = right_dollar_field("amount", "Amount")
1610 class PendingChargeInline(PlStackTabularInline):
1613 verbose_name_plural = "Charges"
1614 verbose_name = "Charge"
1615 exclude = ["invoice"]
1616 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1617 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1618 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1622 def queryset(self, request):
1623 qs = super(PendingChargeInline, self).queryset(request)
1624 qs = qs.filter(state="pending")
1627 dollar_amount = right_dollar_field("amount", "Amount")
1629 class PaymentInline(PlStackTabularInline):
1632 verbose_name_plural = "Payments"
1633 verbose_name = "Payment"
1634 fields = ["date", "dollar_amount"]
1635 readonly_fields = ["date", "dollar_amount"]
1636 suit_classes = 'suit-tab suit-tab-accountpayments'
1640 dollar_amount = right_dollar_field("amount", "Amount")
1642 class AccountAdmin(admin.ModelAdmin):
1643 list_display = ("site", "balance_due")
1645 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1648 (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1650 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1653 ('general','Account Details'),
1654 ('accountinvoice', 'Invoices'),
1655 ('accountpayments', 'Payments'),
1656 ('accountpendingcharges','Pending Charges'),
1659 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1660 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1661 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1663 # Now register the new UserAdmin...
1664 admin.site.register(User, UserAdmin)
1665 # ... and, since we're not using Django's builtin permissions,
1666 # unregister the Group model from admin.
1667 #admin.site.unregister(Group)
1669 #Do not show django evolution in the admin interface
1670 from django_evolution.models import Version, Evolution
1671 #admin.site.unregister(Version)
1672 #admin.site.unregister(Evolution)
1675 # When debugging it is often easier to see all the classes, but for regular use
1676 # only the top-levels should be displayed
1679 admin.site.register(Deployment, DeploymentAdmin)
1680 admin.site.register(Controller, ControllerAdmin)
1681 admin.site.register(Site, SiteAdmin)
1682 admin.site.register(Slice, SliceAdmin)
1683 admin.site.register(Service, ServiceAdmin)
1684 admin.site.register(Reservation, ReservationAdmin)
1685 admin.site.register(Network, NetworkAdmin)
1686 admin.site.register(Router, RouterAdmin)
1687 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1688 admin.site.register(Account, AccountAdmin)
1689 admin.site.register(Invoice, InvoiceAdmin)
1692 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1693 admin.site.register(ServiceClass, ServiceClassAdmin)
1694 #admin.site.register(PlanetStack)
1695 admin.site.register(Tag, TagAdmin)
1696 admin.site.register(ControllerRole)
1697 admin.site.register(SiteRole)
1698 admin.site.register(SliceRole)
1699 admin.site.register(PlanetStackRole)
1700 admin.site.register(Node, NodeAdmin)
1701 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1702 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1703 admin.site.register(Sliver, SliverAdmin)
1704 admin.site.register(Image, ImageAdmin)
1705 admin.site.register(DashboardView, DashboardViewAdmin)
1706 admin.site.register(Flavor, FlavorAdmin)