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', 'controllerNetwork', '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 == 'controllerNetwork':
353 kwargs['queryset'] = Deployment.select_by_acl(request.user)
354 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_deployment_changed(this);"})
355 elif db_field.name == 'flavor':
356 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_flavor_changed(this);"})
358 field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
362 class SiteInline(PlStackTabularInline):
365 suit_classes = 'suit-tab suit-tab-sites'
367 def queryset(self, request):
368 return Site.select_by_user(request.user)
370 class UserInline(PlStackTabularInline):
372 fields = ['backend_status_icon', 'email', 'firstname', 'lastname']
373 readonly_fields = ('backend_status_icon', )
375 suit_classes = 'suit-tab suit-tab-users'
377 def queryset(self, request):
378 return User.select_by_user(request.user)
380 class SliceInline(PlStackTabularInline):
382 fields = ['backend_status_icon', 'name', 'site', 'serviceClass', 'service']
383 readonly_fields = ('backend_status_icon', )
385 suit_classes = 'suit-tab suit-tab-slices'
387 def queryset(self, request):
388 return Slice.select_by_user(request.user)
390 class NodeInline(PlStackTabularInline):
393 suit_classes = 'suit-tab suit-tab-nodes'
394 fields = ['backend_status_icon', 'name', '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 ControllerSiteDeploymentsInline(PlStackTabularInline):
418 model = ControllerSiteDeployments
420 suit_classes = 'suit-tab suit-tab-admin-only'
421 fields = ['controller', 'site_deployment', 'tenant_id']
423 class SitePrivilegeInline(PlStackTabularInline):
424 model = SitePrivilege
426 suit_classes = 'suit-tab suit-tab-siteprivileges'
427 fields = ['backend_status_icon', 'user','site', 'role']
428 readonly_fields = ('backend_status_icon', )
430 def formfield_for_foreignkey(self, db_field, request, **kwargs):
431 if db_field.name == 'site':
432 kwargs['queryset'] = Site.select_by_user(request.user)
434 if db_field.name == 'user':
435 kwargs['queryset'] = User.select_by_user(request.user)
436 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
438 def queryset(self, request):
439 return SitePrivilege.select_by_user(request.user)
441 class SiteDeploymentsInline(PlStackTabularInline):
442 model = SiteDeployments
444 suit_classes = 'suit-tab suit-tab-deployments'
445 fields = ['backend_status_icon', 'deployment','site', 'controller']
446 readonly_fields = ('backend_status_icon', )
448 def formfield_for_foreignkey(self, db_field, request, **kwargs):
449 if db_field.name == 'site':
450 kwargs['queryset'] = Site.select_by_user(request.user)
452 if db_field.name == 'deployment':
453 kwargs['queryset'] = Deployment.select_by_user(request.user)
455 if db_field.name == 'controller':
456 kwargs['queryset'] = Controller.select_by_user(request.user)
458 return super(SiteDeploymentsInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
460 def queryset(self, request):
461 return SiteDeployments.select_by_user(request.user)
464 class SlicePrivilegeInline(PlStackTabularInline):
465 model = SlicePrivilege
466 suit_classes = 'suit-tab suit-tab-sliceprivileges'
468 fields = ('backend_status_icon', 'user', 'slice', 'role')
469 readonly_fields = ('backend_status_icon', )
471 def formfield_for_foreignkey(self, db_field, request, **kwargs):
472 if db_field.name == 'slice':
473 kwargs['queryset'] = Slice.select_by_user(request.user)
474 if db_field.name == 'user':
475 kwargs['queryset'] = User.select_by_user(request.user)
477 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
479 def queryset(self, request):
480 return SlicePrivilege.select_by_user(request.user)
482 class SliceNetworkInline(PlStackTabularInline):
483 model = Network.slices.through
484 selflink_fieldname = "network"
486 verbose_name = "Network Connection"
487 verbose_name_plural = "Network Connections"
488 suit_classes = 'suit-tab suit-tab-slicenetworks'
489 fields = ['backend_status_icon', 'network']
490 readonly_fields = ('backend_status_icon', )
492 class ImageDeploymentsInline(PlStackTabularInline):
493 model = ImageDeployments
495 verbose_name = "Image Deployments"
496 verbose_name_plural = "Image Deployments"
497 suit_classes = 'suit-tab suit-tab-imagedeployments'
498 fields = ['backend_status_icon', 'image', 'deployment']
499 readonly_fields = ['backend_status_icon']
501 class ControllerImagesInline(PlStackTabularInline):
502 model = ControllerImages
504 verbose_name = "Controller Images"
505 verbose_name_plural = "Controller Images"
506 suit_classes = 'suit-tab suit-tab-admin-only'
507 fields = ['backend_status_icon', 'image', 'controller', 'glance_image_id']
508 readonly_fields = ['backend_status_icon', 'glance_image_id']
510 class SliceRoleAdmin(PlanetStackBaseAdmin):
514 class SiteRoleAdmin(PlanetStackBaseAdmin):
518 class DeploymentAdminForm(forms.ModelForm):
519 sites = forms.ModelMultipleChoiceField(
520 queryset=Site.objects.all(),
522 help_text="Select which sites are allowed to host nodes in this deployment",
523 widget=FilteredSelectMultiple(
524 verbose_name=('Sites'), is_stacked=False
527 images = forms.ModelMultipleChoiceField(
528 queryset=Image.objects.all(),
530 help_text="Select which images should be deployed on this deployment",
531 widget=FilteredSelectMultiple(
532 verbose_name=('Images'), is_stacked=False
535 flavors = forms.ModelMultipleChoiceField(
536 queryset=Flavor.objects.all(),
538 help_text="Select which flavors should be usable on this deployment",
539 widget=FilteredSelectMultiple(
540 verbose_name=('Flavors'), is_stacked=False
545 many_to_many = ["flavors",]
547 def __init__(self, *args, **kwargs):
548 request = kwargs.pop('request', None)
549 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
551 self.fields['accessControl'].initial = "allow site " + request.user.site.name
553 if self.instance and self.instance.pk:
554 self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments.all()]
555 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments.all()]
556 self.fields['flavors'].initial = self.instance.flavors.all()
558 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
559 """ helper function for handling m2m relations from the MultipleChoiceField
561 this_obj: the source object we want to link from
563 selected_objs: a list of destination objects we want to link to
565 all_relations: the full set of relations involving this_obj, including ones we don't want
567 relation_class: the class that implements the relation from source to dest
569 local_attrname: field name representing this_obj in relation_class
571 foreign_attrname: field name representing selected_objs in relation_class
573 This function will remove all newobjclass relations from this_obj
574 that are not contained in selected_objs, and add any relations that
575 are in selected_objs but don't exist in the data model yet.
578 existing_dest_objs = []
579 for relation in list(all_relations):
580 if getattr(relation, foreign_attrname) not in selected_objs:
581 #print "deleting site", sdp.site
584 existing_dest_objs.append(getattr(relation, foreign_attrname))
586 for dest_obj in selected_objs:
587 if dest_obj not in existing_dest_objs:
588 #print "adding site", site
589 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
590 relation = relation_class(**kwargs)
593 def save(self, commit=True):
594 deployment = super(DeploymentAdminForm, self).save(commit=False)
598 # this has to be done after save() if/when a deployment is first created
599 deployment.flavors = self.cleaned_data['flavors']
602 # save_m2m() doesn't seem to work with 'through' relations. So we
603 # create/destroy the through models ourselves. There has to be
606 self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments.all(), SiteDeployments, "deployment", "site")
607 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments.all(), ImageDeployments, "deployment", "image")
608 # manipulate_m2m_objs doesn't work for Flavor/Deployment relationship
609 # so well handle that manually here
610 for flavor in deployment.flavors.all():
611 if getattr(flavor, 'name') not in self.cleaned_data['flavors']:
612 deployment.flavors.remove(flavor)
613 for flavor in self.cleaned_data['flavors']:
614 if flavor not in deployment.flavors.all():
615 flavor.deployments.add(deployment)
621 class DeploymentAdminROForm(DeploymentAdminForm):
622 def save(self, commit=True):
623 raise PermissionDenied
625 class SiteAssocInline(PlStackTabularInline):
626 model = Site.deployments.through
628 suit_classes = 'suit-tab suit-tab-sites'
630 class DeploymentAdmin(PlanetStackBaseAdmin):
632 fieldList = ['backend_status_text', 'name', 'sites', 'images', 'flavors', 'accessControl']
633 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
634 # node no longer directly connected to deployment
635 #inlines = [DeploymentPrivilegeInline,NodeInline,TagInline,ImageDeploymentsInline]
636 inlines = [DeploymentPrivilegeInline,TagInline,ImageDeploymentsInline]
637 list_display = ['backend_status_icon', 'name']
638 list_display_links = ('backend_status_icon', 'name', )
639 readonly_fields = ('backend_status_text', )
641 user_readonly_fields = ['name']
643 # nodes no longer direclty connected to deployments
644 #suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags'),('imagedeployments','Images'))
645 suit_form_tabs =(('sites','Deployment Details'),('deploymentprivileges','Privileges'),('tags','Tags'),('imagedeployments','Images'))
647 def get_form(self, request, obj=None, **kwargs):
648 if request.user.isReadOnlyUser():
649 kwargs["form"] = DeploymentAdminROForm
651 kwargs["form"] = DeploymentAdminForm
652 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
654 # from stackexchange: pass the request object into the form
656 class AdminFormMetaClass(adminForm):
657 def __new__(cls, *args, **kwargs):
658 kwargs['request'] = request
659 return adminForm(*args, **kwargs)
661 return AdminFormMetaClass
663 class ControllerAdminForm(forms.ModelForm):
664 site_deployments = forms.ModelMultipleChoiceField(
665 queryset=SiteDeployments.objects.all(),
667 help_text="Select which sites deployments are managed by this controller",
668 widget=FilteredSelectMultiple(
669 verbose_name=('Site Deployments'), is_stacked=False
676 def __init__(self, *args, **kwargs):
677 request = kwargs.pop('request', None)
678 super(ControllerAdminForm, self).__init__(*args, **kwargs)
680 if self.instance and self.instance.pk:
681 self.fields['site_deployments'].initial = [x.site_deployment for x in self.instance.controllersitedeployments.all()]
683 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
684 """ helper function for handling m2m relations from the MultipleChoiceField
685 this_obj: the source object we want to link from
686 selected_objs: a list of destination objects we want to link to
687 all_relations: the full set of relations involving this_obj, including ones we don't want
688 relation_class: the class that implements the relation from source to dest
689 local_attrname: field name representing this_obj in relation_class
690 foreign_attrname: field name representing selected_objs in relation_class
691 This function will remove all newobjclass relations from this_obj
692 that are not contained in selected_objs, and add any relations that
693 are in selected_objs but don't exist in the data model yet.
695 existing_dest_objs = []
696 for relation in list(all_relations):
697 if getattr(relation, foreign_attrname) not in selected_objs:
698 #print "deleting site", sdp.site
701 existing_dest_objs.append(getattr(relation, foreign_attrname))
703 for dest_obj in selected_objs:
704 if dest_obj not in existing_dest_objs:
705 #print "adding site", site
706 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
707 relation = relation_class(**kwargs)
710 def save(self, commit=True):
711 controller = super(ControllerAdminForm, self).save(commit=False)
716 # save_m2m() doesn't seem to work with 'through' relations. So we
717 # create/destroy the through models ourselves. There has to be
719 self.manipulate_m2m_objs(controller, self.cleaned_data['site_deployments'], controller.controllersitedeployments.all(), ControllerSiteDeployments, "controller", "site_deployment")
725 class ControllerAdmin(PlanetStackBaseAdmin):
727 fieldList = ['name', 'version', 'backend_type', 'auth_url', 'admin_user', 'admin_tenant','admin_password']
728 #fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
729 inlines = [ControllerSiteDeploymentsInline] # ,ControllerImagesInline]
730 list_display = ['backend_status_icon', 'name', 'version', 'backend_type']
731 list_display_links = ('backend_status_icon', 'name', )
732 readonly_fields = ('backend_status_text',)
734 user_readonly_fields = []
736 def get_form(self, request, obj=None, **kwargs):
738 if request.user.isReadOnlyUser():
739 kwargs["form"] = ControllerAdminROForm
741 kwargs["form"] = ControllerAdminForm
742 adminForm = super(ControllerAdmin,self).get_form(request, obj, **kwargs)
744 # from stackexchange: pass the request object into the form
746 class AdminFormMetaClass(adminForm):
747 def __new__(cls, *args, **kwargs):
748 kwargs['request'] = request
749 return adminForm(*args, **kwargs)
751 return AdminFormMetaClass
753 class ServiceAttrAsTabInline(PlStackTabularInline):
754 model = ServiceAttribute
755 fields = ['name','value']
757 suit_classes = 'suit-tab suit-tab-serviceattrs'
759 class ServiceAdmin(PlanetStackBaseAdmin):
760 list_display = ("backend_status_icon","name","description","versionNumber","enabled","published")
761 list_display_links = ('backend_status_icon', 'name', )
762 fieldList = ["backend_status_text","name","description","versionNumber","enabled","published"]
763 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
764 inlines = [ServiceAttrAsTabInline,SliceInline]
765 readonly_fields = ('backend_status_text', )
767 user_readonly_fields = fieldList
769 suit_form_tabs =(('general', 'Service Details'),
771 ('serviceattrs','Additional Attributes'),
774 class SiteAdmin(PlanetStackBaseAdmin):
775 fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
777 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
778 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
780 suit_form_tabs =(('general', 'Site Details'),
782 ('siteprivileges','Privileges'),
783 ('deployments','Deployments'),
788 readonly_fields = ['backend_status_text', 'accountLink']
790 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
792 list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
793 list_display_links = ('backend_status_icon', 'name', )
794 filter_horizontal = ('deployments',)
795 inlines = [SliceInline,UserInline,TagInline, SitePrivilegeInline, SiteDeploymentsInline]
796 search_fields = ['name']
798 def queryset(self, request):
799 return Site.select_by_user(request.user)
801 def get_formsets(self, request, obj=None):
802 for inline in self.get_inline_instances(request, obj):
803 # hide MyInline in the add view
806 if isinstance(inline, SliverInline):
807 inline.model.caller = request.user
808 yield inline.get_formset(request, obj)
810 def accountLink(self, obj):
811 link_obj = obj.accounts.all()
813 reverse_path = "admin:core_account_change"
814 url = reverse(reverse_path, args =(link_obj[0].id,))
815 return "<a href='%s'>%s</a>" % (url, "view billing details")
817 return "no billing data for this site"
818 accountLink.allow_tags = True
819 accountLink.short_description = "Billing"
821 def save_model(self, request, obj, form, change):
822 # update openstack connection to use this site/tenant
823 obj.save_by_user(request.user)
825 def delete_model(self, request, obj):
826 obj.delete_by_user(request.user)
829 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
830 fieldList = ['backend_status_text', 'user', 'site', 'role']
832 (None, {'fields': fieldList, 'classes':['collapse']})
834 readonly_fields = ('backend_status_text', )
835 list_display = ('backend_status_icon', 'user', 'site', 'role')
836 list_display_links = list_display
837 user_readonly_fields = fieldList
838 user_readonly_inlines = []
840 def formfield_for_foreignkey(self, db_field, request, **kwargs):
841 if db_field.name == 'site':
842 if not request.user.is_admin:
843 # only show sites where user is an admin or pi
845 for site_privilege in SitePrivilege.objects.filer(user=request.user):
846 if site_privilege.role.role_type in ['admin', 'pi']:
847 sites.add(site_privilege.site)
848 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
850 if db_field.name == 'user':
851 if not request.user.is_admin:
852 # only show users from sites where caller has admin or pi role
853 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
854 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
855 sites = [site_privilege.site for site_privilege in site_privileges]
856 site_privileges = SitePrivilege.objects.filter(site__in=sites)
857 emails = [site_privilege.user.email for site_privilege in site_privileges]
858 users = User.objects.filter(email__in=emails)
859 kwargs['queryset'] = users
861 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
863 def queryset(self, request):
864 # admins can see all privileges. Users can only see privileges at sites
865 # where they have the admin role or pi role.
866 qs = super(SitePrivilegeAdmin, self).queryset(request)
867 #if not request.user.is_admin:
868 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
869 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
870 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
871 # sites = Site.objects.filter(login_base__in=login_bases)
872 # qs = qs.filter(site__in=sites)
875 class SliceForm(forms.ModelForm):
879 'service': LinkedSelect
883 cleaned_data = super(SliceForm, self).clean()
884 name = cleaned_data.get('name')
885 site = cleaned_data.get('site')
886 slice_id = self.instance.id
887 if not site and slice_id:
888 site = Slice.objects.get(id=slice_id).site
889 if (not isinstance(site,Site)):
890 # previous code indicates 'site' could be a site_id and not a site?
891 site = Slice.objects.get(id=site.id)
892 if not name.startswith(site.login_base):
893 raise forms.ValidationError('slice name must begin with %s' % site.login_base)
896 class ControllerSlicesInline(PlStackTabularInline):
897 model = ControllerSlices
899 verbose_name = "Controller Slices"
900 verbose_name_plural = "Controller Slices"
901 suit_classes = 'suit-tab suit-tab-admin-only'
902 fields = ['backend_status_icon', 'controller', 'tenant_id']
903 readonly_fields = ('backend_status_icon', )
905 class SliceAdmin(PlanetStackBaseAdmin):
907 fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
908 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
909 readonly_fields = ('backend_status_text', )
910 list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_slivers')
911 list_display_links = ('backend_status_icon', 'name', )
912 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
913 admin_inlines = [ControllerSlicesInline]
915 user_readonly_fields = fieldList
918 def suit_form_tabs(self):
919 tabs =[('general', 'Slice Details'),
920 ('slicenetworks','Networks'),
921 ('sliceprivileges','Privileges'),
922 ('slivers','Slivers'),
924 ('reservations','Reservations'),
927 request=getattr(_thread_locals, "request", None)
928 if request and request.user.is_admin:
929 tabs.append( ('admin-only', 'Admin-Only') )
933 def add_view(self, request, form_url='', extra_context=None):
934 # revert to default read-only fields
935 self.readonly_fields = ('backend_status_text',)
936 return super(SliceAdmin, self).add_view(request, form_url, extra_context=extra_context)
938 def change_view(self, request, object_id, form_url='', extra_context=None):
939 # cannot change the site of an existing slice so make the site field read only
941 self.readonly_fields = ('backend_status_text','site')
942 return super(SliceAdmin, self).change_view(request, object_id, form_url)
944 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
945 deployment_nodes = []
946 for node in Node.objects.all():
947 deployment_nodes.append( (node.deployment.id, node.id, node.name) )
949 deployment_flavors = []
950 for flavor in Flavor.objects.all():
951 for deployment in flavor.deployments.all():
952 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
954 deployment_images = []
955 for image in Image.objects.all():
956 for deployment_image in image.imagedeployments.all():
957 deployment_images.append( (deployment_image.controller.id, image.id, image.name) )
959 site_login_bases = []
960 for site in Site.objects.all():
961 site_login_bases.append((site.id, site.login_base))
963 context["deployment_nodes"] = deployment_nodes
964 context["deployment_flavors"] = deployment_flavors
965 context["deployment_images"] = deployment_images
966 context["site_login_bases"] = site_login_bases
967 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
969 def formfield_for_foreignkey(self, db_field, request, **kwargs):
970 if db_field.name == 'site':
971 kwargs['queryset'] = Site.select_by_user(request.user)
972 kwargs['widget'] = forms.Select(attrs={'onChange': "update_slice_prefix(this, $($(this).closest('fieldset')[0]).find('.field-name input')[0].id)"})
974 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
976 def queryset(self, request):
977 # admins can see all keys. Users can only see slices they belong to.
978 return Slice.select_by_user(request.user)
980 def get_formsets(self, request, obj=None):
981 for inline in self.get_inline_instances(request, obj):
982 # hide MyInline in the add view
985 if isinstance(inline, SliverInline):
986 inline.model.caller = request.user
987 yield inline.get_formset(request, obj)
989 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
991 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
993 readonly_fields = ('backend_status_text', )
994 list_display = ('backend_status_icon', 'user', 'slice', 'role')
995 list_display_links = list_display
997 user_readonly_fields = ['user', 'slice', 'role']
998 user_readonly_inlines = []
1000 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1001 if db_field.name == 'slice':
1002 kwargs['queryset'] = Slice.select_by_user(request.user)
1004 if db_field.name == 'user':
1005 kwargs['queryset'] = User.select_by_user(request.user)
1007 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1009 def queryset(self, request):
1010 # admins can see all memberships. Users can only see memberships of
1011 # slices where they have the admin role.
1012 return SlicePrivilege.select_by_user(request.user)
1014 def save_model(self, request, obj, form, change):
1015 # update openstack connection to use this site/tenant
1016 auth = request.session.get('auth', {})
1017 auth['tenant'] = obj.slice.slicename
1018 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1021 def delete_model(self, request, obj):
1022 # update openstack connection to use this site/tenant
1023 auth = request.session.get('auth', {})
1024 auth['tenant'] = obj.slice.slicename
1025 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1029 class ImageAdmin(PlanetStackBaseAdmin):
1031 fieldsets = [('Image Details',
1032 {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
1033 'classes': ['suit-tab suit-tab-general']})
1035 readonly_fields = ('backend_status_text', )
1037 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'), ('controllerimages', 'Controllers'))
1039 inlines = [SliverInline, ControllerImagesInline]
1041 user_readonly_fields = ['name', 'disk_format', 'container_format']
1043 list_display = ['backend_status_icon', 'name']
1044 list_display_links = ('backend_status_icon', 'name', )
1046 class NodeForm(forms.ModelForm):
1049 'site': LinkedSelect,
1050 'deployment': LinkedSelect
1053 class NodeAdmin(PlanetStackBaseAdmin):
1055 list_display = ('backend_status_icon', 'name', 'site_deployment')
1056 list_display_links = ('backend_status_icon', 'name', )
1057 list_filter = ('site_deployment',)
1059 inlines = [TagInline,SliverInline]
1060 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site_deployment'], 'classes':['suit-tab suit-tab-details']})]
1061 readonly_fields = ('backend_status_text', )
1063 user_readonly_fields = ['name','site_deployment']
1064 user_readonly_inlines = [TagInline,SliverInline]
1066 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
1069 class SliverForm(forms.ModelForm):
1072 ip = forms.CharField(widget=PlainTextWidget)
1073 instance_name = forms.CharField(widget=PlainTextWidget)
1075 'ip': PlainTextWidget(),
1076 'instance_name': PlainTextWidget(),
1077 'slice': LinkedSelect,
1078 'controllerNetwork': LinkedSelect,
1079 'node': LinkedSelect,
1080 'image': LinkedSelect
1083 class TagAdmin(PlanetStackBaseAdmin):
1084 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
1085 list_display_links = list_display
1086 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
1087 user_readonly_inlines = []
1089 class SliverAdmin(PlanetStackBaseAdmin):
1092 ('Sliver Details', {'fields': ['backend_status_text', 'slice', 'controllerNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
1094 readonly_fields = ('backend_status_text', )
1095 list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'flavor', 'image', 'node', 'controllerNetwork']
1096 list_display_links = ('backend_status_icon', 'ip',)
1098 suit_form_tabs =(('general', 'Sliver Details'),
1102 inlines = [TagInline]
1104 user_readonly_fields = ['slice', 'controllerNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image']
1106 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1107 if db_field.name == 'slice':
1108 kwargs['queryset'] = Slice.select_by_user(request.user)
1110 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1112 def queryset(self, request):
1113 # admins can see all slivers. Users can only see slivers of
1114 # the slices they belong to.
1115 return Sliver.select_by_user(request.user)
1118 def get_formsets(self, request, obj=None):
1119 # make some fields read only if we are updating an existing record
1121 #self.readonly_fields = ('ip', 'instance_name')
1122 self.readonly_fields = ('backend_status_text',)
1124 self.readonly_fields = ('backend_status_text',)
1125 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
1127 for inline in self.get_inline_instances(request, obj):
1128 # hide MyInline in the add view
1131 if isinstance(inline, SliverInline):
1132 inline.model.caller = request.user
1133 yield inline.get_formset(request, obj)
1135 #def save_model(self, request, obj, form, change):
1136 # # update openstack connection to use this site/tenant
1137 # auth = request.session.get('auth', {})
1138 # auth['tenant'] = obj.slice.name
1139 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1140 # obj.creator = request.user
1143 #def delete_model(self, request, obj):
1144 # # update openstack connection to use this site/tenant
1145 # auth = request.session.get('auth', {})
1146 # auth['tenant'] = obj.slice.name
1147 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1150 class UserCreationForm(forms.ModelForm):
1151 """A form for creating new users. Includes all the required
1152 fields, plus a repeated password."""
1153 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
1154 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
1158 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
1160 def clean_password2(self):
1161 # Check that the two password entries match
1162 password1 = self.cleaned_data.get("password1")
1163 password2 = self.cleaned_data.get("password2")
1164 if password1 and password2 and password1 != password2:
1165 raise forms.ValidationError("Passwords don't match")
1168 def save(self, commit=True):
1169 # Save the provided password in hashed format
1170 user = super(UserCreationForm, self).save(commit=False)
1171 user.password = self.cleaned_data["password1"]
1172 #user.set_password(self.cleaned_data["password1"])
1178 class UserChangeForm(forms.ModelForm):
1179 """A form for updating users. Includes all the fields on
1180 the user, but replaces the password field with admin's
1181 password hash display field.
1183 password = ReadOnlyPasswordHashField(label='Password',
1184 help_text= '<a href=\"password/\">Change Password</a>.')
1188 widgets = { 'public_key': UploadTextareaWidget, }
1190 def clean_password(self):
1191 # Regardless of what the user provides, return the initial value.
1192 # This is done here, rather than on the field, because the
1193 # field does not have access to the initial value
1194 return self.initial["password"]
1196 class UserDashboardViewInline(PlStackTabularInline):
1197 model = UserDashboardView
1199 suit_classes = 'suit-tab suit-tab-dashboards'
1200 fields = ['user', 'dashboardView', 'order']
1202 class UserAdmin(PermissionCheckingAdminMixin, UserAdmin):
1203 # Note: Make sure PermissionCheckingAdminMixin is listed before
1204 # admin.ModelAdmin in the class declaration.
1209 # The forms to add and change user instances
1210 form = UserChangeForm
1211 add_form = UserCreationForm
1213 # The fields to be used in displaying the User model.
1214 # These override the definitions on the base UserAdmin
1215 # that reference specific fields on auth.User.
1216 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
1217 list_filter = ('site',)
1218 inlines = [SlicePrivilegeInline,SitePrivilegeInline,ControllerPrivilegeInline,UserDashboardViewInline]
1220 fieldListLoginDetails = ['backend_status_text', 'email','site','password','is_active','is_readonly','is_admin','public_key']
1221 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1224 ('Login Details', {'fields': ['backend_status_text', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1225 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1226 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1227 #('Important dates', {'fields': ('last_login',)}),
1231 'classes': ('wide',),
1232 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')},
1235 readonly_fields = ('backend_status_text', )
1236 search_fields = ('email',)
1237 ordering = ('email',)
1238 filter_horizontal = ()
1240 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1243 def suit_form_tabs(self):
1244 if getattr(_thread_locals, "obj", None) is None:
1247 return (('general','Login Details'),
1248 ('contact','Contact Information'),
1249 ('sliceprivileges','Slice Privileges'),
1250 ('siteprivileges','Site Privileges'),
1251 ('controllerprivileges','Controller Privileges'),
1252 ('dashboards','Dashboard Views'))
1254 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1255 if db_field.name == 'site':
1256 kwargs['queryset'] = Site.select_by_user(request.user)
1258 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1260 def queryset(self, request):
1261 return User.select_by_user(request.user)
1263 class DashboardViewAdmin(PlanetStackBaseAdmin):
1264 fieldsets = [('Dashboard View Details',
1265 {'fields': ['backend_status_text', 'name', 'url'],
1266 'classes': ['suit-tab suit-tab-general']})
1268 readonly_fields = ('backend_status_text', )
1270 suit_form_tabs =(('general','Dashboard View Details'),)
1272 class ServiceResourceInline(PlStackTabularInline):
1273 model = ServiceResource
1276 class ServiceClassAdmin(PlanetStackBaseAdmin):
1277 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1278 list_display_links = ('backend_status_icon', 'name', )
1279 inlines = [ServiceResourceInline]
1281 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1282 user_readonly_inlines = []
1284 class ReservedResourceInline(PlStackTabularInline):
1285 model = ReservedResource
1287 suit_classes = 'suit-tab suit-tab-reservedresources'
1289 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1290 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1292 if db_field.name == 'resource':
1293 # restrict resources to those that the slice's service class allows
1294 if request._slice is not None:
1295 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1296 if len(field.queryset) > 0:
1297 field.initial = field.queryset.all()[0]
1299 field.queryset = field.queryset.none()
\r
1300 elif db_field.name == 'sliver':
\r
1301 # restrict slivers to those that belong to the slice
\r
1302 if request._slice is not None:
\r
1303 field.queryset = field.queryset.filter(slice = request._slice)
1305 field.queryset = field.queryset.none()
\r
1309 def queryset(self, request):
1310 return ReservedResource.select_by_user(request.user)
1312 class ReservationChangeForm(forms.ModelForm):
1316 'slice' : LinkedSelect
1319 class ReservationAddForm(forms.ModelForm):
1320 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1321 refresh = forms.CharField(widget=forms.HiddenInput())
1324 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1326 def clean_slice(self):
1327 slice = self.cleaned_data.get("slice")
1328 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1330 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1336 'slice' : LinkedSelect
1340 class ReservationAddRefreshForm(ReservationAddForm):
1341 """ This form is displayed when the Reservation Form receives an update
1342 from the Slice dropdown onChange handler. It doesn't validate the
1343 data and doesn't save the data. This will cause the form to be
1347 """ don't validate anything other than slice """
1348 dont_validate_fields = ("startTime", "duration")
1350 def full_clean(self):
1351 result = super(ReservationAddForm, self).full_clean()
1353 for fieldname in self.dont_validate_fields:
1354 if fieldname in self._errors:
1355 del self._errors[fieldname]
1359 """ don't save anything """
1363 class ReservationAdmin(PlanetStackBaseAdmin):
1364 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
1365 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1366 readonly_fields = ('backend_status_text', )
1367 list_display = ('startTime', 'duration')
1368 form = ReservationAddForm
1370 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1372 inlines = [ReservedResourceInline]
1373 user_readonly_fields = fieldList
1375 def add_view(self, request, form_url='', extra_context=None):
1376 timezone.activate(request.user.timezone)
1377 request._refresh = False
1378 request._slice = None
1379 if request.method == 'POST':
1380 # "refresh" will be set to "1" if the form was submitted due to
1381 # a change in the Slice dropdown.
1382 if request.POST.get("refresh","1") == "1":
1383 request._refresh = True
1384 request.POST["refresh"] = "0"
1386 # Keep track of the slice that was selected, so the
1387 # reservedResource inline can filter items for the slice.
1388 request._slice = request.POST.get("slice",None)
1389 if (request._slice is not None):
1390 request._slice = Slice.objects.get(id=request._slice)
1392 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1395 def changelist_view(self, request, extra_context = None):
1396 timezone.activate(request.user.timezone)
1397 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1399 def get_form(self, request, obj=None, **kwargs):
1402 # For changes, set request._slice to the slice already set in the
1404 request._slice = obj.slice
1405 self.form = ReservationChangeForm
1407 if getattr(request, "_refresh", False):
1408 self.form = ReservationAddRefreshForm
1410 self.form = ReservationAddForm
1411 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1413 def get_readonly_fields(self, request, obj=None):
1414 if (obj is not None):
1415 # Prevent slice from being changed after the reservation has been
1421 def queryset(self, request):
1422 return Reservation.select_by_user(request.user)
1424 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1425 list_display = ("backend_status_icon", "name", )
1426 list_display_links = ('backend_status_icon', 'name', )
1427 user_readonly_fields = ['name']
1428 user_readonly_inlines = []
1430 class RouterAdmin(PlanetStackBaseAdmin):
1431 list_display = ("backend_status_icon", "name", )
1432 list_display_links = ('backend_status_icon', 'name', )
1433 user_readonly_fields = ['name']
1434 user_readonly_inlines = []
1436 class RouterInline(PlStackTabularInline):
1437 model = Router.networks.through
1439 verbose_name_plural = "Routers"
1440 verbose_name = "Router"
1441 suit_classes = 'suit-tab suit-tab-routers'
1443 class NetworkParameterInline(PlStackGenericTabularInline):
1444 model = NetworkParameter
1446 verbose_name_plural = "Parameters"
1447 verbose_name = "Parameter"
1448 suit_classes = 'suit-tab suit-tab-netparams'
1449 fields = ['backend_status_icon', 'parameter', 'value']
1450 readonly_fields = ('backend_status_icon', )
1452 class NetworkSliversInline(PlStackTabularInline):
1453 fields = ['backend_status_icon', 'network','sliver','ip']
1454 readonly_fields = ("backend_status_icon", "ip", )
1455 model = NetworkSliver
1456 selflink_fieldname = "sliver"
1458 verbose_name_plural = "Slivers"
1459 verbose_name = "Sliver"
1460 suit_classes = 'suit-tab suit-tab-networkslivers'
1462 class NetworkSlicesInline(PlStackTabularInline):
1463 model = NetworkSlice
1464 selflink_fieldname = "slice"
1466 verbose_name_plural = "Slices"
1467 verbose_name = "Slice"
1468 suit_classes = 'suit-tab suit-tab-networkslices'
1469 fields = ['backend_status_icon', 'network','slice']
1470 readonly_fields = ('backend_status_icon', )
1472 class ControllerNetworksInline(PlStackTabularInline):
1473 model = ControllerNetworks
1475 verbose_name_plural = "Controller Networks"
1476 verbose_name = "Controller Network"
1477 suit_classes = 'suit-tab suit-tab-admin-only'
1478 fields = ['backend_status_icon', 'controller','net_id','subnet_id']
1479 readonly_fields = ('backend_status_icon', )
1481 class NetworkForm(forms.ModelForm):
1485 'topologyParameters': UploadTextareaWidget,
1486 'controllerParameters': UploadTextareaWidget,
1489 class NetworkAdmin(PlanetStackBaseAdmin):
1490 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1491 list_display_links = ('backend_status_icon', 'name', )
1492 readonly_fields = ("subnet", )
1494 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1495 admin_inlines = [ControllerNetworksInline]
1500 (None, {'fields': ['backend_status_text', 'name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet'],
1501 'classes':['suit-tab suit-tab-general']}),
1502 (None, {'fields': ['topologyParameters', 'controllerUrl', 'controllerParameters'],
1503 'classes':['suit-tab suit-tab-sdn']}),
1506 readonly_fields = ('backend_status_text', )
1507 user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1510 def suit_form_tabs(self):
1511 tabs=[('general','Network Details'),
1512 ('sdn', 'SDN Configuration'),
1513 ('netparams', 'Parameters'),
1514 ('networkslivers','Slivers'),
1515 ('networkslices','Slices'),
1516 ('routers','Routers'),
1519 request=getattr(_thread_locals, "request", None)
1520 if request and request.user.is_admin:
1521 tabs.append( ('admin-only', 'Admin-Only') )
1526 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1527 list_display = ("backend_status_icon", "name", "guaranteedBandwidth", "visibility")
1528 list_display_links = ('backend_status_icon', 'name', )
1529 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1530 user_readonly_inlines = []
1532 (None, {'fields': ['name', 'description', 'guaranteedBandwidth', 'visibility', 'translation', 'sharedNetworkName', 'sharedNetworkId', 'topologyKind', 'controllerKind'],
1533 'classes':['suit-tab suit-tab-general']}),]
1534 suit_form_tabs = (('general','Network Template Details'), )
1536 class FlavorAdmin(PlanetStackBaseAdmin):
1537 list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1538 list_display_links = ("backend_status_icon", "name")
1539 user_readonly_fields = ("name", "flavor")
1540 fields = ("name", "description", "flavor", "order", "default")
1542 # register a signal that caches the user's credentials when they log in
1543 def cache_credentials(sender, user, request, **kwds):
1544 auth = {'username': request.POST['username'],
1545 'password': request.POST['password']}
1546 request.session['auth'] = auth
1547 user_logged_in.connect(cache_credentials)
1549 def dollar_field(fieldName, short_description):
1550 def newFunc(self, obj):
1552 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1554 x=getattr(obj, fieldName, 0.0)
1556 newFunc.short_description = short_description
1559 def right_dollar_field(fieldName, short_description):
1560 def newFunc(self, obj):
1562 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1563 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1565 x=getattr(obj, fieldName, 0.0)
1567 newFunc.short_description = short_description
1568 newFunc.allow_tags = True
1571 class InvoiceChargeInline(PlStackTabularInline):
1574 verbose_name_plural = "Charges"
1575 verbose_name = "Charge"
1576 exclude = ['account']
1577 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1578 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1582 dollar_amount = right_dollar_field("amount", "Amount")
1584 class InvoiceAdmin(admin.ModelAdmin):
1585 list_display = ("date", "account")
1587 inlines = [InvoiceChargeInline]
1589 fields = ["date", "account", "dollar_amount"]
1590 readonly_fields = ["date", "account", "dollar_amount"]
1592 dollar_amount = dollar_field("amount", "Amount")
1594 class InvoiceInline(PlStackTabularInline):
1597 verbose_name_plural = "Invoices"
1598 verbose_name = "Invoice"
1599 fields = ["date", "dollar_amount"]
1600 readonly_fields = ["date", "dollar_amount"]
1601 suit_classes = 'suit-tab suit-tab-accountinvoice'
1605 dollar_amount = right_dollar_field("amount", "Amount")
1607 class PendingChargeInline(PlStackTabularInline):
1610 verbose_name_plural = "Charges"
1611 verbose_name = "Charge"
1612 exclude = ["invoice"]
1613 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1614 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1615 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1619 def queryset(self, request):
1620 qs = super(PendingChargeInline, self).queryset(request)
1621 qs = qs.filter(state="pending")
1624 dollar_amount = right_dollar_field("amount", "Amount")
1626 class PaymentInline(PlStackTabularInline):
1629 verbose_name_plural = "Payments"
1630 verbose_name = "Payment"
1631 fields = ["date", "dollar_amount"]
1632 readonly_fields = ["date", "dollar_amount"]
1633 suit_classes = 'suit-tab suit-tab-accountpayments'
1637 dollar_amount = right_dollar_field("amount", "Amount")
1639 class AccountAdmin(admin.ModelAdmin):
1640 list_display = ("site", "balance_due")
1642 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1645 (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1647 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1650 ('general','Account Details'),
1651 ('accountinvoice', 'Invoices'),
1652 ('accountpayments', 'Payments'),
1653 ('accountpendingcharges','Pending Charges'),
1656 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1657 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1658 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1660 # Now register the new UserAdmin...
1661 admin.site.register(User, UserAdmin)
1662 # ... and, since we're not using Django's builtin permissions,
1663 # unregister the Group model from admin.
1664 #admin.site.unregister(Group)
1666 #Do not show django evolution in the admin interface
1667 from django_evolution.models import Version, Evolution
1668 #admin.site.unregister(Version)
1669 #admin.site.unregister(Evolution)
1672 # When debugging it is often easier to see all the classes, but for regular use
1673 # only the top-levels should be displayed
1676 admin.site.register(Deployment, DeploymentAdmin)
1677 admin.site.register(Controller, ControllerAdmin)
1678 admin.site.register(Site, SiteAdmin)
1679 admin.site.register(Slice, SliceAdmin)
1680 admin.site.register(Service, ServiceAdmin)
1681 admin.site.register(Reservation, ReservationAdmin)
1682 admin.site.register(Network, NetworkAdmin)
1683 admin.site.register(Router, RouterAdmin)
1684 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1685 admin.site.register(Account, AccountAdmin)
1686 admin.site.register(Invoice, InvoiceAdmin)
1689 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1690 admin.site.register(ServiceClass, ServiceClassAdmin)
1691 #admin.site.register(PlanetStack)
1692 admin.site.register(Tag, TagAdmin)
1693 admin.site.register(ControllerRole)
1694 admin.site.register(SiteRole)
1695 admin.site.register(SliceRole)
1696 admin.site.register(PlanetStackRole)
1697 admin.site.register(Node, NodeAdmin)
1698 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1699 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1700 admin.site.register(Sliver, SliverAdmin)
1701 admin.site.register(Image, ImageAdmin)
1702 admin.site.register(DashboardView, DashboardViewAdmin)
1703 admin.site.register(Flavor, FlavorAdmin)