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 ControllerSiteInline(PlStackTabularInline):
418 model = ControllerSite
420 suit_classes = 'suit-tab suit-tab-admin-only'
421 fields = ['controller', 'site_deployment', 'tenant_id']
424 class SitePrivilegeInline(PlStackTabularInline):
425 model = SitePrivilege
427 suit_classes = 'suit-tab suit-tab-siteprivileges'
428 fields = ['backend_status_icon', 'user','site', 'role']
429 readonly_fields = ('backend_status_icon', )
431 def formfield_for_foreignkey(self, db_field, request, **kwargs):
432 if db_field.name == 'site':
433 kwargs['queryset'] = Site.select_by_user(request.user)
435 if db_field.name == 'user':
436 kwargs['queryset'] = User.select_by_user(request.user)
437 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
439 def queryset(self, request):
440 return SitePrivilege.select_by_user(request.user)
442 class SiteDeploymentInline(PlStackTabularInline):
443 model = SiteDeployment
445 suit_classes = 'suit-tab suit-tab-deployments'
446 fields = ['backend_status_icon', 'deployment','site', 'controller']
447 readonly_fields = ('backend_status_icon', )
449 def formfield_for_foreignkey(self, db_field, request, **kwargs):
450 if db_field.name == 'site':
451 kwargs['queryset'] = Site.select_by_user(request.user)
453 if db_field.name == 'deployment':
454 kwargs['queryset'] = Deployment.select_by_user(request.user)
456 if db_field.name == 'controller':
457 kwargs['queryset'] = Controller.select_by_user(request.user)
459 return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
461 def queryset(self, request):
462 return SiteDeployment.select_by_user(request.user)
465 class SlicePrivilegeInline(PlStackTabularInline):
466 model = SlicePrivilege
467 suit_classes = 'suit-tab suit-tab-sliceprivileges'
469 fields = ('backend_status_icon', 'user', 'slice', 'role')
470 readonly_fields = ('backend_status_icon', )
472 def formfield_for_foreignkey(self, db_field, request, **kwargs):
473 if db_field.name == 'slice':
474 kwargs['queryset'] = Slice.select_by_user(request.user)
475 if db_field.name == 'user':
476 kwargs['queryset'] = User.select_by_user(request.user)
478 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
480 def queryset(self, request):
481 return SlicePrivilege.select_by_user(request.user)
483 class SliceNetworkInline(PlStackTabularInline):
484 model = Network.slices.through
485 selflink_fieldname = "network"
487 verbose_name = "Network Connection"
488 verbose_name_plural = "Network Connections"
489 suit_classes = 'suit-tab suit-tab-slicenetworks'
490 fields = ['backend_status_icon', 'network']
491 readonly_fields = ('backend_status_icon', )
493 class ImageDeploymentsInline(PlStackTabularInline):
494 model = ImageDeployments
496 verbose_name = "Image Deployments"
497 verbose_name_plural = "Image Deployments"
498 suit_classes = 'suit-tab suit-tab-imagedeployments'
499 fields = ['backend_status_icon', 'image', 'deployment']
500 readonly_fields = ['backend_status_icon']
502 class ControllerImagesInline(PlStackTabularInline):
503 model = ControllerImages
505 verbose_name = "Controller Images"
506 verbose_name_plural = "Controller Images"
507 suit_classes = 'suit-tab suit-tab-admin-only'
508 fields = ['backend_status_icon', 'image', 'controller', 'glance_image_id']
509 readonly_fields = ['backend_status_icon', 'glance_image_id']
511 class SliceRoleAdmin(PlanetStackBaseAdmin):
515 class SiteRoleAdmin(PlanetStackBaseAdmin):
519 class DeploymentAdminForm(forms.ModelForm):
520 sites = forms.ModelMultipleChoiceField(
521 queryset=Site.objects.all(),
523 help_text="Select which sites are allowed to host nodes in this deployment",
524 widget=FilteredSelectMultiple(
525 verbose_name=('Sites'), is_stacked=False
528 images = forms.ModelMultipleChoiceField(
529 queryset=Image.objects.all(),
531 help_text="Select which images should be deployed on this deployment",
532 widget=FilteredSelectMultiple(
533 verbose_name=('Images'), is_stacked=False
536 flavors = forms.ModelMultipleChoiceField(
537 queryset=Flavor.objects.all(),
539 help_text="Select which flavors should be usable on this deployment",
540 widget=FilteredSelectMultiple(
541 verbose_name=('Flavors'), is_stacked=False
546 many_to_many = ["flavors",]
548 def __init__(self, *args, **kwargs):
549 request = kwargs.pop('request', None)
550 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
552 self.fields['accessControl'].initial = "allow site " + request.user.site.name
554 if self.instance and self.instance.pk:
555 self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments.all()]
556 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments.all()]
557 self.fields['flavors'].initial = self.instance.flavors.all()
559 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
560 """ helper function for handling m2m relations from the MultipleChoiceField
562 this_obj: the source object we want to link from
564 selected_objs: a list of destination objects we want to link to
566 all_relations: the full set of relations involving this_obj, including ones we don't want
568 relation_class: the class that implements the relation from source to dest
570 local_attrname: field name representing this_obj in relation_class
572 foreign_attrname: field name representing selected_objs in relation_class
574 This function will remove all newobjclass relations from this_obj
575 that are not contained in selected_objs, and add any relations that
576 are in selected_objs but don't exist in the data model yet.
579 existing_dest_objs = []
580 for relation in list(all_relations):
581 if getattr(relation, foreign_attrname) not in selected_objs:
582 #print "deleting site", sdp.site
585 existing_dest_objs.append(getattr(relation, foreign_attrname))
587 for dest_obj in selected_objs:
588 if dest_obj not in existing_dest_objs:
589 #print "adding site", site
590 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
591 relation = relation_class(**kwargs)
594 def save(self, commit=True):
595 deployment = super(DeploymentAdminForm, self).save(commit=False)
599 # this has to be done after save() if/when a deployment is first created
600 deployment.flavors = self.cleaned_data['flavors']
603 # save_m2m() doesn't seem to work with 'through' relations. So we
604 # create/destroy the through models ourselves. There has to be
607 self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments.all(), SiteDeployment, "deployment", "site")
608 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments.all(), ImageDeployments, "deployment", "image")
609 # manipulate_m2m_objs doesn't work for Flavor/Deployment relationship
610 # so well handle that manually here
611 for flavor in deployment.flavors.all():
612 if getattr(flavor, 'name') not in self.cleaned_data['flavors']:
613 deployment.flavors.remove(flavor)
614 for flavor in self.cleaned_data['flavors']:
615 if flavor not in deployment.flavors.all():
616 flavor.deployments.add(deployment)
622 class DeploymentAdminROForm(DeploymentAdminForm):
623 def save(self, commit=True):
624 raise PermissionDenied
626 class SiteAssocInline(PlStackTabularInline):
627 model = Site.deployments.through
629 suit_classes = 'suit-tab suit-tab-sites'
631 class DeploymentAdmin(PlanetStackBaseAdmin):
633 fieldList = ['backend_status_text', 'name', 'sites', 'images', 'flavors', 'accessControl']
634 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
635 # node no longer directly connected to deployment
636 #inlines = [DeploymentPrivilegeInline,NodeInline,TagInline,ImageDeploymentsInline]
637 inlines = [DeploymentPrivilegeInline,TagInline,ImageDeploymentsInline]
638 list_display = ['backend_status_icon', 'name']
639 list_display_links = ('backend_status_icon', 'name', )
640 readonly_fields = ('backend_status_text', )
642 user_readonly_fields = ['name']
644 # nodes no longer direclty connected to deployments
645 #suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags'),('imagedeployments','Images'))
646 suit_form_tabs =(('sites','Deployment Details'),('deploymentprivileges','Privileges'),('tags','Tags'),('imagedeployments','Images'))
648 def get_form(self, request, obj=None, **kwargs):
649 if request.user.isReadOnlyUser():
650 kwargs["form"] = DeploymentAdminROForm
652 kwargs["form"] = DeploymentAdminForm
653 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
655 # from stackexchange: pass the request object into the form
657 class AdminFormMetaClass(adminForm):
658 def __new__(cls, *args, **kwargs):
659 kwargs['request'] = request
660 return adminForm(*args, **kwargs)
662 return AdminFormMetaClass
664 class ControllerAdminForm(forms.ModelForm):
665 site_deployments = forms.ModelMultipleChoiceField(
666 queryset=SiteDeployment.objects.all(),
668 help_text="Select which sites deployments are managed by this controller",
669 widget=FilteredSelectMultiple(
670 verbose_name=('Site Deployments'), is_stacked=False
677 def __init__(self, *args, **kwargs):
678 request = kwargs.pop('request', None)
679 super(ControllerAdminForm, self).__init__(*args, **kwargs)
681 if self.instance and self.instance.pk:
682 self.fields['site_deployments'].initial = [x.site_deployment for x in self.instance.controllersitedeployments.all()]
684 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
685 """ helper function for handling m2m relations from the MultipleChoiceField
686 this_obj: the source object we want to link from
687 selected_objs: a list of destination objects we want to link to
688 all_relations: the full set of relations involving this_obj, including ones we don't want
689 relation_class: the class that implements the relation from source to dest
690 local_attrname: field name representing this_obj in relation_class
691 foreign_attrname: field name representing selected_objs in relation_class
692 This function will remove all newobjclass relations from this_obj
693 that are not contained in selected_objs, and add any relations that
694 are in selected_objs but don't exist in the data model yet.
696 existing_dest_objs = []
697 for relation in list(all_relations):
698 if getattr(relation, foreign_attrname) not in selected_objs:
699 #print "deleting site", sdp.site
702 existing_dest_objs.append(getattr(relation, foreign_attrname))
704 for dest_obj in selected_objs:
705 if dest_obj not in existing_dest_objs:
706 #print "adding site", site
707 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
708 relation = relation_class(**kwargs)
711 def save(self, commit=True):
712 controller = super(ControllerAdminForm, self).save(commit=False)
717 # save_m2m() doesn't seem to work with 'through' relations. So we
718 # create/destroy the through models ourselves. There has to be
720 self.manipulate_m2m_objs(controller, self.cleaned_data['site_deployments'], controller.controllersitedeployments.all(), ControllerSite, "controller", "site_deployment")
727 class ControllerAdmin(PlanetStackBaseAdmin):
729 fieldList = ['name', 'version', 'backend_type', 'auth_url', 'admin_user', 'admin_tenant','admin_password']
730 #fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
731 inlines = [ControllerSiteInline] # ,ControllerImagesInline]
732 list_display = ['backend_status_icon', 'name', 'version', 'backend_type']
733 list_display_links = ('backend_status_icon', 'name', )
734 readonly_fields = ('backend_status_text',)
736 user_readonly_fields = []
738 def get_form(self, request, obj=None, **kwargs):
740 if request.user.isReadOnlyUser():
741 kwargs["form"] = ControllerAdminROForm
743 kwargs["form"] = ControllerAdminForm
744 adminForm = super(ControllerAdmin,self).get_form(request, obj, **kwargs)
746 # from stackexchange: pass the request object into the form
748 class AdminFormMetaClass(adminForm):
749 def __new__(cls, *args, **kwargs):
750 kwargs['request'] = request
751 return adminForm(*args, **kwargs)
753 return AdminFormMetaClass
755 class ServiceAttrAsTabInline(PlStackTabularInline):
756 model = ServiceAttribute
757 fields = ['name','value']
759 suit_classes = 'suit-tab suit-tab-serviceattrs'
761 class ServiceAdmin(PlanetStackBaseAdmin):
762 list_display = ("backend_status_icon","name","description","versionNumber","enabled","published")
763 list_display_links = ('backend_status_icon', 'name', )
764 fieldList = ["backend_status_text","name","description","versionNumber","enabled","published"]
765 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
766 inlines = [ServiceAttrAsTabInline,SliceInline]
767 readonly_fields = ('backend_status_text', )
769 user_readonly_fields = fieldList
771 suit_form_tabs =(('general', 'Service Details'),
773 ('serviceattrs','Additional Attributes'),
776 class SiteAdmin(PlanetStackBaseAdmin):
777 fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
779 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
780 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
782 suit_form_tabs =(('general', 'Site Details'),
784 ('siteprivileges','Privileges'),
785 ('deployments','Deployments'),
790 readonly_fields = ['backend_status_text', 'accountLink']
792 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
794 list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
795 list_display_links = ('backend_status_icon', 'name', )
796 filter_horizontal = ('deployments',)
797 inlines = [SliceInline,UserInline,TagInline, SitePrivilegeInline, SiteDeploymentInline]
798 search_fields = ['name']
800 def queryset(self, request):
801 return Site.select_by_user(request.user)
803 def get_formsets(self, request, obj=None):
804 for inline in self.get_inline_instances(request, obj):
805 # hide MyInline in the add view
808 if isinstance(inline, SliverInline):
809 inline.model.caller = request.user
810 yield inline.get_formset(request, obj)
812 def accountLink(self, obj):
813 link_obj = obj.accounts.all()
815 reverse_path = "admin:core_account_change"
816 url = reverse(reverse_path, args =(link_obj[0].id,))
817 return "<a href='%s'>%s</a>" % (url, "view billing details")
819 return "no billing data for this site"
820 accountLink.allow_tags = True
821 accountLink.short_description = "Billing"
823 def save_model(self, request, obj, form, change):
824 # update openstack connection to use this site/tenant
825 obj.save_by_user(request.user)
827 def delete_model(self, request, obj):
828 obj.delete_by_user(request.user)
831 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
832 fieldList = ['backend_status_text', 'user', 'site', 'role']
834 (None, {'fields': fieldList, 'classes':['collapse']})
836 readonly_fields = ('backend_status_text', )
837 list_display = ('backend_status_icon', 'user', 'site', 'role')
838 list_display_links = list_display
839 user_readonly_fields = fieldList
840 user_readonly_inlines = []
842 def formfield_for_foreignkey(self, db_field, request, **kwargs):
843 if db_field.name == 'site':
844 if not request.user.is_admin:
845 # only show sites where user is an admin or pi
847 for site_privilege in SitePrivilege.objects.filer(user=request.user):
848 if site_privilege.role.role_type in ['admin', 'pi']:
849 sites.add(site_privilege.site)
850 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
852 if db_field.name == 'user':
853 if not request.user.is_admin:
854 # only show users from sites where caller has admin or pi role
855 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
856 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
857 sites = [site_privilege.site for site_privilege in site_privileges]
858 site_privileges = SitePrivilege.objects.filter(site__in=sites)
859 emails = [site_privilege.user.email for site_privilege in site_privileges]
860 users = User.objects.filter(email__in=emails)
861 kwargs['queryset'] = users
863 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
865 def queryset(self, request):
866 # admins can see all privileges. Users can only see privileges at sites
867 # where they have the admin role or pi role.
868 qs = super(SitePrivilegeAdmin, self).queryset(request)
869 #if not request.user.is_admin:
870 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
871 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
872 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
873 # sites = Site.objects.filter(login_base__in=login_bases)
874 # qs = qs.filter(site__in=sites)
877 class SliceForm(forms.ModelForm):
881 'service': LinkedSelect
885 cleaned_data = super(SliceForm, self).clean()
886 name = cleaned_data.get('name')
887 site = cleaned_data.get('site')
888 slice_id = self.instance.id
889 if not site and slice_id:
890 site = Slice.objects.get(id=slice_id).site
891 if (not isinstance(site,Site)):
892 # previous code indicates 'site' could be a site_id and not a site?
893 site = Slice.objects.get(id=site.id)
894 if not name.startswith(site.login_base):
895 raise forms.ValidationError('slice name must begin with %s' % site.login_base)
898 class ControllerSliceInline(PlStackTabularInline):
899 model = ControllerSlice
901 verbose_name = "Controller Slices"
902 verbose_name_plural = "Controller Slices"
903 suit_classes = 'suit-tab suit-tab-admin-only'
904 fields = ['backend_status_icon', 'controller', 'tenant_id']
905 readonly_fields = ('backend_status_icon', )
907 class SliceAdmin(PlanetStackBaseAdmin):
909 fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
910 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
911 readonly_fields = ('backend_status_text', )
912 list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_slivers')
913 list_display_links = ('backend_status_icon', 'name', )
914 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
915 admin_inlines = [ControllerSliceInline]
917 user_readonly_fields = fieldList
920 def suit_form_tabs(self):
921 tabs =[('general', 'Slice Details'),
922 ('slicenetworks','Networks'),
923 ('sliceprivileges','Privileges'),
924 ('slivers','Slivers'),
926 ('reservations','Reservations'),
929 request=getattr(_thread_locals, "request", None)
930 if request and request.user.is_admin:
931 tabs.append( ('admin-only', 'Admin-Only') )
935 def add_view(self, request, form_url='', extra_context=None):
936 # revert to default read-only fields
937 self.readonly_fields = ('backend_status_text',)
938 return super(SliceAdmin, self).add_view(request, form_url, extra_context=extra_context)
940 def change_view(self, request, object_id, form_url='', extra_context=None):
941 # cannot change the site of an existing slice so make the site field read only
943 self.readonly_fields = ('backend_status_text','site')
944 return super(SliceAdmin, self).change_view(request, object_id, form_url)
946 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
947 deployment_nodes = []
948 for node in Node.objects.all():
949 deployment_nodes.append( (node.site_deployment.id, node.id, node.name) )
951 deployment_flavors = []
952 for flavor in Flavor.objects.all():
953 for deployment in flavor.deployments.all():
954 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
956 deployment_images = []
957 for image in Image.objects.all():
958 for deployment_image in image.imagedeployments.all():
959 deployment_images.append( (deployment_image.deployment.id, image.id, image.name) )
961 site_login_bases = []
962 for site in Site.objects.all():
963 site_login_bases.append((site.id, site.login_base))
965 context["deployment_nodes"] = deployment_nodes
966 context["deployment_flavors"] = deployment_flavors
967 context["deployment_images"] = deployment_images
968 context["site_login_bases"] = site_login_bases
969 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
971 def formfield_for_foreignkey(self, db_field, request, **kwargs):
972 if db_field.name == 'site':
973 kwargs['queryset'] = Site.select_by_user(request.user)
974 kwargs['widget'] = forms.Select(attrs={'onChange': "update_slice_prefix(this, $($(this).closest('fieldset')[0]).find('.field-name input')[0].id)"})
976 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
978 def queryset(self, request):
979 # admins can see all keys. Users can only see slices they belong to.
980 return Slice.select_by_user(request.user)
982 def get_formsets(self, request, obj=None):
983 for inline in self.get_inline_instances(request, obj):
984 # hide MyInline in the add view
987 if isinstance(inline, SliverInline):
988 inline.model.caller = request.user
989 yield inline.get_formset(request, obj)
991 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
993 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
995 readonly_fields = ('backend_status_text', )
996 list_display = ('backend_status_icon', 'user', 'slice', 'role')
997 list_display_links = list_display
999 user_readonly_fields = ['user', 'slice', 'role']
1000 user_readonly_inlines = []
1002 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1003 if db_field.name == 'slice':
1004 kwargs['queryset'] = Slice.select_by_user(request.user)
1006 if db_field.name == 'user':
1007 kwargs['queryset'] = User.select_by_user(request.user)
1009 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1011 def queryset(self, request):
1012 # admins can see all memberships. Users can only see memberships of
1013 # slices where they have the admin role.
1014 return SlicePrivilege.select_by_user(request.user)
1016 def save_model(self, request, obj, form, change):
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)
1023 def delete_model(self, request, obj):
1024 # update openstack connection to use this site/tenant
1025 auth = request.session.get('auth', {})
1026 auth['tenant'] = obj.slice.slicename
1027 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1031 class ImageAdmin(PlanetStackBaseAdmin):
1033 fieldsets = [('Image Details',
1034 {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
1035 'classes': ['suit-tab suit-tab-general']})
1037 readonly_fields = ('backend_status_text', )
1039 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'), ('controllerimages', 'Controllers'))
1041 inlines = [SliverInline, ControllerImagesInline]
1043 user_readonly_fields = ['name', 'disk_format', 'container_format']
1045 list_display = ['backend_status_icon', 'name']
1046 list_display_links = ('backend_status_icon', 'name', )
1048 class NodeForm(forms.ModelForm):
1051 'site': LinkedSelect,
1052 'deployment': LinkedSelect
1055 class NodeAdmin(PlanetStackBaseAdmin):
1057 list_display = ('backend_status_icon', 'name', 'site_deployment')
1058 list_display_links = ('backend_status_icon', 'name', )
1059 list_filter = ('site_deployment',)
1061 inlines = [TagInline,SliverInline]
1062 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site_deployment'], 'classes':['suit-tab suit-tab-details']})]
1063 readonly_fields = ('backend_status_text', )
1065 user_readonly_fields = ['name','site_deployment']
1066 user_readonly_inlines = [TagInline,SliverInline]
1068 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
1071 class SliverForm(forms.ModelForm):
1074 ip = forms.CharField(widget=PlainTextWidget)
1075 instance_name = forms.CharField(widget=PlainTextWidget)
1077 'ip': PlainTextWidget(),
1078 'instance_name': PlainTextWidget(),
1079 'slice': LinkedSelect,
1080 'deployment': LinkedSelect,
1081 'node': LinkedSelect,
1082 'image': LinkedSelect
1085 class TagAdmin(PlanetStackBaseAdmin):
1086 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
1087 list_display_links = list_display
1088 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
1089 user_readonly_inlines = []
1091 class SliverAdmin(PlanetStackBaseAdmin):
1094 ('Sliver Details', {'fields': ['backend_status_text', 'slice', 'deployment', 'node', 'ip', 'instance_name', 'flavor', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
1096 readonly_fields = ('backend_status_text', )
1097 list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'flavor', 'image', 'node', 'deployment']
1098 list_display_links = ('backend_status_icon', 'ip',)
1100 suit_form_tabs =(('general', 'Sliver Details'),
1104 inlines = [TagInline]
1106 user_readonly_fields = ['slice', 'deployment', 'node', 'ip', 'instance_name', 'flavor', 'image']
1108 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1109 if db_field.name == 'slice':
1110 kwargs['queryset'] = Slice.select_by_user(request.user)
1112 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1114 def queryset(self, request):
1115 # admins can see all slivers. Users can only see slivers of
1116 # the slices they belong to.
1117 return Sliver.select_by_user(request.user)
1120 def get_formsets(self, request, obj=None):
1121 # make some fields read only if we are updating an existing record
1123 #self.readonly_fields = ('ip', 'instance_name')
1124 self.readonly_fields = ('backend_status_text',)
1126 self.readonly_fields = ('backend_status_text',)
1127 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
1129 for inline in self.get_inline_instances(request, obj):
1130 # hide MyInline in the add view
1133 if isinstance(inline, SliverInline):
1134 inline.model.caller = request.user
1135 yield inline.get_formset(request, obj)
1137 #def save_model(self, request, obj, form, change):
1138 # # update openstack connection to use this site/tenant
1139 # auth = request.session.get('auth', {})
1140 # auth['tenant'] = obj.slice.name
1141 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1142 # obj.creator = request.user
1145 #def delete_model(self, request, obj):
1146 # # update openstack connection to use this site/tenant
1147 # auth = request.session.get('auth', {})
1148 # auth['tenant'] = obj.slice.name
1149 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1152 class UserCreationForm(forms.ModelForm):
1153 """A form for creating new users. Includes all the required
1154 fields, plus a repeated password."""
1155 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
1156 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
1160 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
1162 def clean_password2(self):
1163 # Check that the two password entries match
1164 password1 = self.cleaned_data.get("password1")
1165 password2 = self.cleaned_data.get("password2")
1166 if password1 and password2 and password1 != password2:
1167 raise forms.ValidationError("Passwords don't match")
1170 def save(self, commit=True):
1171 # Save the provided password in hashed format
1172 user = super(UserCreationForm, self).save(commit=False)
1173 user.password = self.cleaned_data["password1"]
1174 #user.set_password(self.cleaned_data["password1"])
1180 class UserChangeForm(forms.ModelForm):
1181 """A form for updating users. Includes all the fields on
1182 the user, but replaces the password field with admin's
1183 password hash display field.
1185 password = ReadOnlyPasswordHashField(label='Password',
1186 help_text= '<a href=\"password/\">Change Password</a>.')
1190 widgets = { 'public_key': UploadTextareaWidget, }
1192 def clean_password(self):
1193 # Regardless of what the user provides, return the initial value.
1194 # This is done here, rather than on the field, because the
1195 # field does not have access to the initial value
1196 return self.initial["password"]
1198 class UserDashboardViewInline(PlStackTabularInline):
1199 model = UserDashboardView
1201 suit_classes = 'suit-tab suit-tab-dashboards'
1202 fields = ['user', 'dashboardView', 'order']
1204 class UserAdmin(PermissionCheckingAdminMixin, UserAdmin):
1205 # Note: Make sure PermissionCheckingAdminMixin is listed before
1206 # admin.ModelAdmin in the class declaration.
1211 # The forms to add and change user instances
1212 form = UserChangeForm
1213 add_form = UserCreationForm
1215 # The fields to be used in displaying the User model.
1216 # These override the definitions on the base UserAdmin
1217 # that reference specific fields on auth.User.
1218 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
1219 list_filter = ('site',)
1220 inlines = [SlicePrivilegeInline,SitePrivilegeInline,ControllerPrivilegeInline,UserDashboardViewInline]
1222 fieldListLoginDetails = ['backend_status_text', 'email','site','password','is_active','is_readonly','is_admin','public_key']
1223 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1226 ('Login Details', {'fields': ['backend_status_text', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1227 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1228 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1229 #('Important dates', {'fields': ('last_login',)}),
1233 'classes': ('wide',),
1234 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')},
1237 readonly_fields = ('backend_status_text', )
1238 search_fields = ('email',)
1239 ordering = ('email',)
1240 filter_horizontal = ()
1242 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1245 def suit_form_tabs(self):
1246 if getattr(_thread_locals, "obj", None) is None:
1249 return (('general','Login Details'),
1250 ('contact','Contact Information'),
1251 ('sliceprivileges','Slice Privileges'),
1252 ('siteprivileges','Site Privileges'),
1253 ('controllerprivileges','Controller Privileges'),
1254 ('dashboards','Dashboard Views'))
1256 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1257 if db_field.name == 'site':
1258 kwargs['queryset'] = Site.select_by_user(request.user)
1260 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1262 def queryset(self, request):
1263 return User.select_by_user(request.user)
1265 class ControllerDashboardViewInline(PlStackTabularInline):
1266 model = ControllerDashboardView
1268 fields = ["controller", "url"]
1269 suit_classes = 'suit-tab suit-tab-controllers'
1271 class DashboardViewAdmin(PlanetStackBaseAdmin):
1272 fieldsets = [('Dashboard View Details',
1273 {'fields': ['backend_status_text', 'name', 'url'],
1274 'classes': ['suit-tab suit-tab-general']})
1276 readonly_fields = ('backend_status_text', )
1277 inlines = [ControllerDashboardViewInline]
1279 suit_form_tabs =(('general','Dashboard View Details'),
1280 ('controllers', 'Per-controller Dashboard Details'))
1282 class ServiceResourceInline(PlStackTabularInline):
1283 model = ServiceResource
1286 class ServiceClassAdmin(PlanetStackBaseAdmin):
1287 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1288 list_display_links = ('backend_status_icon', 'name', )
1289 inlines = [ServiceResourceInline]
1291 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1292 user_readonly_inlines = []
1294 class ReservedResourceInline(PlStackTabularInline):
1295 model = ReservedResource
1297 suit_classes = 'suit-tab suit-tab-reservedresources'
1299 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1300 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1302 if db_field.name == 'resource':
1303 # restrict resources to those that the slice's service class allows
1304 if request._slice is not None:
1305 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1306 if len(field.queryset) > 0:
1307 field.initial = field.queryset.all()[0]
1309 field.queryset = field.queryset.none()
\r
1310 elif db_field.name == 'sliver':
\r
1311 # restrict slivers to those that belong to the slice
\r
1312 if request._slice is not None:
\r
1313 field.queryset = field.queryset.filter(slice = request._slice)
1315 field.queryset = field.queryset.none()
\r
1319 def queryset(self, request):
1320 return ReservedResource.select_by_user(request.user)
1322 class ReservationChangeForm(forms.ModelForm):
1326 'slice' : LinkedSelect
1329 class ReservationAddForm(forms.ModelForm):
1330 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1331 refresh = forms.CharField(widget=forms.HiddenInput())
1334 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1336 def clean_slice(self):
1337 slice = self.cleaned_data.get("slice")
1338 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1340 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1346 'slice' : LinkedSelect
1350 class ReservationAddRefreshForm(ReservationAddForm):
1351 """ This form is displayed when the Reservation Form receives an update
1352 from the Slice dropdown onChange handler. It doesn't validate the
1353 data and doesn't save the data. This will cause the form to be
1357 """ don't validate anything other than slice """
1358 dont_validate_fields = ("startTime", "duration")
1360 def full_clean(self):
1361 result = super(ReservationAddForm, self).full_clean()
1363 for fieldname in self.dont_validate_fields:
1364 if fieldname in self._errors:
1365 del self._errors[fieldname]
1369 """ don't save anything """
1373 class ReservationAdmin(PlanetStackBaseAdmin):
1374 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
1375 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1376 readonly_fields = ('backend_status_text', )
1377 list_display = ('startTime', 'duration')
1378 form = ReservationAddForm
1380 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1382 inlines = [ReservedResourceInline]
1383 user_readonly_fields = fieldList
1385 def add_view(self, request, form_url='', extra_context=None):
1386 timezone.activate(request.user.timezone)
1387 request._refresh = False
1388 request._slice = None
1389 if request.method == 'POST':
1390 # "refresh" will be set to "1" if the form was submitted due to
1391 # a change in the Slice dropdown.
1392 if request.POST.get("refresh","1") == "1":
1393 request._refresh = True
1394 request.POST["refresh"] = "0"
1396 # Keep track of the slice that was selected, so the
1397 # reservedResource inline can filter items for the slice.
1398 request._slice = request.POST.get("slice",None)
1399 if (request._slice is not None):
1400 request._slice = Slice.objects.get(id=request._slice)
1402 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1405 def changelist_view(self, request, extra_context = None):
1406 timezone.activate(request.user.timezone)
1407 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1409 def get_form(self, request, obj=None, **kwargs):
1412 # For changes, set request._slice to the slice already set in the
1414 request._slice = obj.slice
1415 self.form = ReservationChangeForm
1417 if getattr(request, "_refresh", False):
1418 self.form = ReservationAddRefreshForm
1420 self.form = ReservationAddForm
1421 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1423 def get_readonly_fields(self, request, obj=None):
1424 if (obj is not None):
1425 # Prevent slice from being changed after the reservation has been
1431 def queryset(self, request):
1432 return Reservation.select_by_user(request.user)
1434 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1435 list_display = ("backend_status_icon", "name", )
1436 list_display_links = ('backend_status_icon', 'name', )
1437 user_readonly_fields = ['name']
1438 user_readonly_inlines = []
1440 class RouterAdmin(PlanetStackBaseAdmin):
1441 list_display = ("backend_status_icon", "name", )
1442 list_display_links = ('backend_status_icon', 'name', )
1443 user_readonly_fields = ['name']
1444 user_readonly_inlines = []
1446 class RouterInline(PlStackTabularInline):
1447 model = Router.networks.through
1449 verbose_name_plural = "Routers"
1450 verbose_name = "Router"
1451 suit_classes = 'suit-tab suit-tab-routers'
1453 class NetworkParameterInline(PlStackGenericTabularInline):
1454 model = NetworkParameter
1456 verbose_name_plural = "Parameters"
1457 verbose_name = "Parameter"
1458 suit_classes = 'suit-tab suit-tab-netparams'
1459 fields = ['backend_status_icon', 'parameter', 'value']
1460 readonly_fields = ('backend_status_icon', )
1462 class NetworkSliversInline(PlStackTabularInline):
1463 fields = ['backend_status_icon', 'network','sliver','ip']
1464 readonly_fields = ("backend_status_icon", "ip", )
1465 model = NetworkSliver
1466 selflink_fieldname = "sliver"
1468 verbose_name_plural = "Slivers"
1469 verbose_name = "Sliver"
1470 suit_classes = 'suit-tab suit-tab-networkslivers'
1472 class NetworkSlicesInline(PlStackTabularInline):
1473 model = NetworkSlice
1474 selflink_fieldname = "slice"
1476 verbose_name_plural = "Slices"
1477 verbose_name = "Slice"
1478 suit_classes = 'suit-tab suit-tab-networkslices'
1479 fields = ['backend_status_icon', 'network','slice']
1480 readonly_fields = ('backend_status_icon', )
1482 class ControllerNetworkInline(PlStackTabularInline):
1483 model = ControllerNetwork
1485 verbose_name_plural = "Controller Networks"
1486 verbose_name = "Controller Network"
1487 suit_classes = 'suit-tab suit-tab-admin-only'
1488 fields = ['backend_status_icon', 'controller','net_id','subnet_id']
1489 readonly_fields = ('backend_status_icon', )
1491 class NetworkForm(forms.ModelForm):
1495 'topologyParameters': UploadTextareaWidget,
1496 'controllerParameters': UploadTextareaWidget,
1499 class NetworkAdmin(PlanetStackBaseAdmin):
1500 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1501 list_display_links = ('backend_status_icon', 'name', )
1502 readonly_fields = ("subnet", )
1504 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1505 admin_inlines = [ControllerNetworkInline]
1510 (None, {'fields': ['backend_status_text', 'name','template','ports','labels','owner','guaranteed_bandwidth', 'permit_all_slices','permitted_slices','network_id','router_id','subnet_id','subnet'],
1511 'classes':['suit-tab suit-tab-general']}),
1512 (None, {'fields': ['topology_parameters', 'controller_url', 'controller_parameters'],
1513 'classes':['suit-tab suit-tab-sdn']}),
1516 readonly_fields = ('backend_status_text', )
1517 user_readonly_fields = ['name','template','ports','labels','owner','guaranteed_bandwidth', 'permit_all_slices','permitted_slices','network_id','router_id','subnet_id','subnet']
1520 def suit_form_tabs(self):
1521 tabs=[('general','Network Details'),
1522 ('sdn', 'SDN Configuration'),
1523 ('netparams', 'Parameters'),
1524 ('networkslivers','Slivers'),
1525 ('networkslices','Slices'),
1526 ('routers','Routers'),
1529 request=getattr(_thread_locals, "request", None)
1530 if request and request.user.is_admin:
1531 tabs.append( ('admin-only', 'Admin-Only') )
1536 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1537 list_display = ("backend_status_icon", "name", "guaranteed_bandwidth", "visibility")
1538 list_display_links = ('backend_status_icon', 'name', )
1539 user_readonly_fields = ["name", "guaranteed_bandwidth", "visibility"]
1540 user_readonly_inlines = []
1542 (None, {'fields': ['name', 'description', 'guaranteed_bandwidth', 'visibility', 'translation', 'shared_network_name', 'shared_network_id', 'topology_kind', 'controller_kind'],
1543 'classes':['suit-tab suit-tab-general']}),]
1544 suit_form_tabs = (('general','Network Template Details'), )
1546 class FlavorAdmin(PlanetStackBaseAdmin):
1547 list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1548 list_display_links = ("backend_status_icon", "name")
1549 user_readonly_fields = ("name", "flavor")
1550 fields = ("name", "description", "flavor", "order", "default")
1552 # register a signal that caches the user's credentials when they log in
1553 def cache_credentials(sender, user, request, **kwds):
1554 auth = {'username': request.POST['username'],
1555 'password': request.POST['password']}
1556 request.session['auth'] = auth
1557 user_logged_in.connect(cache_credentials)
1559 def dollar_field(fieldName, short_description):
1560 def newFunc(self, obj):
1562 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1564 x=getattr(obj, fieldName, 0.0)
1566 newFunc.short_description = short_description
1569 def right_dollar_field(fieldName, short_description):
1570 def newFunc(self, obj):
1572 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1573 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1575 x=getattr(obj, fieldName, 0.0)
1577 newFunc.short_description = short_description
1578 newFunc.allow_tags = True
1581 class InvoiceChargeInline(PlStackTabularInline):
1584 verbose_name_plural = "Charges"
1585 verbose_name = "Charge"
1586 exclude = ['account']
1587 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1588 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1592 dollar_amount = right_dollar_field("amount", "Amount")
1594 class InvoiceAdmin(admin.ModelAdmin):
1595 list_display = ("date", "account")
1597 inlines = [InvoiceChargeInline]
1599 fields = ["date", "account", "dollar_amount"]
1600 readonly_fields = ["date", "account", "dollar_amount"]
1602 dollar_amount = dollar_field("amount", "Amount")
1604 class InvoiceInline(PlStackTabularInline):
1607 verbose_name_plural = "Invoices"
1608 verbose_name = "Invoice"
1609 fields = ["date", "dollar_amount"]
1610 readonly_fields = ["date", "dollar_amount"]
1611 suit_classes = 'suit-tab suit-tab-accountinvoice'
1615 dollar_amount = right_dollar_field("amount", "Amount")
1617 class PendingChargeInline(PlStackTabularInline):
1620 verbose_name_plural = "Charges"
1621 verbose_name = "Charge"
1622 exclude = ["invoice"]
1623 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1624 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1625 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1629 def queryset(self, request):
1630 qs = super(PendingChargeInline, self).queryset(request)
1631 qs = qs.filter(state="pending")
1634 dollar_amount = right_dollar_field("amount", "Amount")
1636 class PaymentInline(PlStackTabularInline):
1639 verbose_name_plural = "Payments"
1640 verbose_name = "Payment"
1641 fields = ["date", "dollar_amount"]
1642 readonly_fields = ["date", "dollar_amount"]
1643 suit_classes = 'suit-tab suit-tab-accountpayments'
1647 dollar_amount = right_dollar_field("amount", "Amount")
1649 class AccountAdmin(admin.ModelAdmin):
1650 list_display = ("site", "balance_due")
1652 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1655 (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1657 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1660 ('general','Account Details'),
1661 ('accountinvoice', 'Invoices'),
1662 ('accountpayments', 'Payments'),
1663 ('accountpendingcharges','Pending Charges'),
1666 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1667 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1668 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1670 # Now register the new UserAdmin...
1671 admin.site.register(User, UserAdmin)
1672 # ... and, since we're not using Django's builtin permissions,
1673 # unregister the Group model from admin.
1674 #admin.site.unregister(Group)
1676 #Do not show django evolution in the admin interface
1677 from django_evolution.models import Version, Evolution
1678 #admin.site.unregister(Version)
1679 #admin.site.unregister(Evolution)
1682 # When debugging it is often easier to see all the classes, but for regular use
1683 # only the top-levels should be displayed
1686 admin.site.register(Deployment, DeploymentAdmin)
1687 admin.site.register(Controller, ControllerAdmin)
1688 admin.site.register(Site, SiteAdmin)
1689 admin.site.register(Slice, SliceAdmin)
1690 admin.site.register(Service, ServiceAdmin)
1691 admin.site.register(Reservation, ReservationAdmin)
1692 admin.site.register(Network, NetworkAdmin)
1693 admin.site.register(Router, RouterAdmin)
1694 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1695 admin.site.register(Account, AccountAdmin)
1696 admin.site.register(Invoice, InvoiceAdmin)
1699 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1700 admin.site.register(ServiceClass, ServiceClassAdmin)
1701 #admin.site.register(PlanetStack)
1702 admin.site.register(Tag, TagAdmin)
1703 admin.site.register(ControllerRole)
1704 admin.site.register(SiteRole)
1705 admin.site.register(SliceRole)
1706 admin.site.register(PlanetStackRole)
1707 admin.site.register(Node, NodeAdmin)
1708 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1709 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1710 admin.site.register(Sliver, SliverAdmin)
1711 admin.site.register(Image, ImageAdmin)
1712 admin.site.register(DashboardView, DashboardViewAdmin)
1713 admin.site.register(Flavor, FlavorAdmin)