1 from core.models import Site
2 from core.models import *
3 from openstack.manager import OpenStackManager
5 from django.contrib import admin
6 from django.contrib.auth.models import Group
7 from django import forms
8 from django.utils.safestring import mark_safe
9 from django.contrib.auth.admin import UserAdmin
10 from django.contrib.admin.widgets import FilteredSelectMultiple, AdminTextareaWidget
11 from django.contrib.auth.forms import ReadOnlyPasswordHashField, AdminPasswordChangeForm
12 from django.contrib.auth.signals import user_logged_in
13 from django.utils import timezone
14 from django.contrib.contenttypes import generic
15 from suit.widgets import LinkedSelect
16 from django.core.exceptions import PermissionDenied
17 from django.core.urlresolvers import reverse, NoReverseMatch
18 from django.utils.encoding import force_text, python_2_unicode_compatible
19 from django.utils.html import conditional_escape, format_html
20 from django.forms.utils import flatatt, to_current_timezone
21 from cgi import escape as html_escape
23 import django_evolution
26 # thread locals necessary to work around a django-suit issue
27 _thread_locals = threading.local()
29 def backend_icon(obj): # backend_status, enacted, updated):
30 #return "%s %s %s" % (str(obj.updated), str(obj.enacted), str(obj.backend_status))
31 if (obj.enacted is not None) and obj.enacted >= obj.updated:
32 return '<span style="min-width:16px;"><img src="/static/admin/img/icon_success.gif"></span>'
34 if obj.backend_status == "Provisioning in progress" or obj.backend_status=="":
35 return '<span style="min-width:16px;" title="%s"><img src="/static/admin/img/icon_clock.gif"></span>' % obj.backend_status
37 return '<span style="min-width:16px;" title="%s"><img src="/static/admin/img/icon_error.gif"></span>' % html_escape(obj.backend_status, quote=True)
39 def backend_text(obj):
40 icon = backend_icon(obj)
41 if (obj.enacted is not None) and obj.enacted >= obj.updated:
42 return "%s %s" % (icon, "successfully enacted")
44 return "%s %s" % (icon, html_escape(obj.backend_status, quote=True))
46 class UploadTextareaWidget(AdminTextareaWidget):
47 def render(self, name, value, attrs=None):
50 final_attrs = self.build_attrs(attrs, name=name)
\r
51 return format_html('<input type="file" style="width: 0; height: 0" id="btn_upload_%s" onChange="uploadTextarea(event,\'%s\');">' \
\r
52 '<button onClick="$(\'#btn_upload_%s\').click(); return false;">Upload</button>' \
\r
53 '<br><textarea{0}>\r\n{1}</textarea>' % (attrs["id"], attrs["id"], attrs["id"]),
\r
54 flatatt(final_attrs),
\r
57 class PlainTextWidget(forms.HiddenInput):
60 def render(self, name, value, attrs=None):
63 return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
65 class PermissionCheckingAdminMixin(object):
66 # call save_by_user and delete_by_user instead of save and delete
68 def has_add_permission(self, request, obj=None):
69 return (not self.__user_is_readonly(request))
71 def has_delete_permission(self, request, obj=None):
72 return (not self.__user_is_readonly(request))
74 def save_model(self, request, obj, form, change):
75 if self.__user_is_readonly(request):
76 # this 'if' might be redundant if save_by_user is implemented right
77 raise PermissionDenied
79 obj.caller = request.user
80 # update openstack connection to use this site/tenant
81 obj.save_by_user(request.user)
83 def delete_model(self, request, obj):
84 obj.delete_by_user(request.user)
86 def save_formset(self, request, form, formset, change):
87 instances = formset.save(commit=False)
88 for instance in instances:
89 instance.save_by_user(request.user)
91 # BUG in django 1.7? Objects are not deleted by formset.save if
92 # commit is False. So let's delete them ourselves.
94 # code from forms/models.py save_existing_objects()
96 forms_to_delete = formset.deleted_forms
\r
97 except AttributeError:
\r
99 if formset.initial_forms:
100 for form in formset.initial_forms:
102 if form in forms_to_delete:
105 formset.deleted_objects.append(obj)
110 def get_actions(self,request):
111 actions = super(PermissionCheckingAdminMixin,self).get_actions(request)
113 if self.__user_is_readonly(request):
114 if 'delete_selected' in actions:
115 del actions['delete_selected']
119 def change_view(self,request,object_id, extra_context=None):
120 if self.__user_is_readonly(request):
121 if not hasattr(self, "readonly_save"):
\r
122 # save the original readonly fields
\r
123 self.readonly_save = self.readonly_fields
\r
124 self.inlines_save = self.inlines
\r
125 if hasattr(self, "user_readonly_fields"):
\r
126 self.readonly_fields=self.user_readonly_fields
\r
127 if hasattr(self, "user_readonly_inlines"):
\r
128 self.inlines = self.user_readonly_inlines
\r
130 if hasattr(self, "readonly_save"):
\r
131 # restore the original readonly fields
\r
132 self.readonly_fields = self.readonly_save
\r
133 if hasattr(self, "inlines_save"):
\r
134 self.inlines = self.inlines_save
137 return super(PermissionCheckingAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
138 except PermissionDenied:
140 if request.method == 'POST':
141 raise PermissionDenied
142 request.readonly = True
143 return super(PermissionCheckingAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
145 def __user_is_readonly(self, request):
146 return request.user.isReadOnlyUser()
148 def backend_status_text(self, obj):
149 return mark_safe(backend_text(obj))
151 def backend_status_icon(self, obj):
152 return mark_safe(backend_icon(obj))
153 backend_status_icon.short_description = ""
155 def get_form(self, request, obj=None, **kwargs):
156 # Save obj and request in thread-local storage, so suit_form_tabs can
157 # use it to determine whether we're in edit or add mode, and can
158 # determine whether the user is an admin.
159 _thread_locals.request = request
160 _thread_locals.obj = obj
161 return super(PermissionCheckingAdminMixin, self).get_form(request, obj, **kwargs)
163 def get_inline_instances(self, request, obj=None):
164 inlines = super(PermissionCheckingAdminMixin, self).get_inline_instances(request, obj)
166 # inlines that should only be shown to an admin user
167 if request.user.is_admin:
168 for inline_class in getattr(self, "admin_inlines", []):
169 inlines.append(inline_class(self.model, self.admin_site))
173 class ReadOnlyAwareAdmin(PermissionCheckingAdminMixin, admin.ModelAdmin):
174 # Note: Make sure PermissionCheckingAdminMixin is listed before
175 # admin.ModelAdmin in the class declaration.
179 class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
182 class SingletonAdmin (ReadOnlyAwareAdmin):
183 def has_add_permission(self, request):
184 if not super(SingletonAdmin, self).has_add_permission(request):
187 num_objects = self.model.objects.count()
193 class PlStackTabularInline(admin.TabularInline):
194 def __init__(self, *args, **kwargs):
195 super(PlStackTabularInline, self).__init__(*args, **kwargs)
197 # InlineModelAdmin as no get_fields() method, so in order to add
198 # the selflink field, we override __init__ to modify self.fields and
199 # self.readonly_fields.
201 self.setup_selflink()
203 def get_change_url(self, model, id):
204 """ Get the URL to a change form in the admin for this model """
205 reverse_path = "admin:%s_change" % (model._meta.db_table)
207 url = reverse(reverse_path, args=(id,))
208 except NoReverseMatch:
213 def setup_selflink(self):
214 if hasattr(self, "selflink_fieldname"):
215 """ self.selflink_model can be defined to punch through a relation
216 to its target object. For example, in SliceNetworkInline, set
217 selflink_model = "network", and the URL will lead to the Network
218 object instead of trying to bring up a change view of the
221 self.selflink_model = getattr(self.model,self.selflink_fieldname).field.rel.to
223 self.selflink_model = self.model
225 url = self.get_change_url(self.selflink_model, 0)
227 # We don't have an admin for this object, so don't create the
232 # Since we need to add "selflink" to the field list, we need to create
233 # self.fields if it is None.
234 if (self.fields is None):
236 for f in self.model._meta.fields:
237 if f.editable and f.name != "id":
238 self.fields.append(f.name)
240 self.fields = tuple(self.fields) + ("selflink", )
242 if self.readonly_fields is None:
243 self.readonly_fields = ()
245 self.readonly_fields = tuple(self.readonly_fields) + ("selflink", )
247 def selflink(self, obj):
248 if hasattr(self, "selflink_fieldname"):
249 obj = getattr(obj, self.selflink_fieldname)
252 url = self.get_change_url(self.selflink_model, obj.id)
253 return "<a href='%s'>Details</a>" % str(url)
255 return "Not present"
\r
257 selflink.allow_tags = True
258 selflink.short_description = "Details"
260 def has_add_permission(self, request):
261 return not request.user.isReadOnlyUser()
263 def get_readonly_fields(self, request, obj=None):
264 readonly_fields = list(self.readonly_fields)[:]
265 if request.user.isReadOnlyUser():
266 for field in self.fields:
267 if not field in readonly_fields:
268 readonly_fields.append(field)
269 return readonly_fields
271 def backend_status_icon(self, obj):
272 return mark_safe(backend_icon(obj))
273 backend_status_icon.short_description = ""
275 class PlStackGenericTabularInline(generic.GenericTabularInline):
276 def has_add_permission(self, request):
277 return not request.user.isReadOnlyUser()
279 def get_readonly_fields(self, request, obj=None):
280 readonly_fields = list(self.readonly_fields)[:]
281 if request.user.isReadOnlyUser():
282 for field in self.fields:
283 if not field in readonly_fields:
284 readonly_fields.append(field)
285 return readonly_fields
287 def backend_status_icon(self, obj):
288 return mark_safe(backend_icon(obj))
289 backend_status_icon.short_description = ""
291 class ReservationInline(PlStackTabularInline):
294 suit_classes = 'suit-tab suit-tab-reservations'
296 def queryset(self, request):
297 return Reservation.select_by_user(request.user)
299 class TagInline(PlStackGenericTabularInline):
302 suit_classes = 'suit-tab suit-tab-tags'
303 fields = ['service', 'name', 'value']
305 def queryset(self, request):
306 return Tag.select_by_user(request.user)
308 class NetworkLookerUpper:
309 """ This is a callable that looks up a network name in a sliver and returns
310 the ip address for that network.
313 byNetworkName = {} # class variable
315 def __init__(self, name):
316 self.short_description = name
318 self.network_name = name
320 def __call__(self, obj):
322 for nbs in obj.networksliver_set.all():
323 if (nbs.network.name == self.network_name):
328 return self.network_name
331 def get(network_name):
332 """ We want to make sure we alwars return the same NetworkLookerUpper
333 because sometimes django will cause them to be instantiated multiple
334 times (and we don't want different ones in form.fields vs
335 SliverInline.readonly_fields).
337 if network_name not in NetworkLookerUpper.byNetworkName:
338 NetworkLookerUpper.byNetworkName[network_name] = NetworkLookerUpper(network_name)
339 return NetworkLookerUpper.byNetworkName[network_name]
341 class SliverInline(PlStackTabularInline):
343 fields = ['backend_status_icon', 'all_ips_string', 'instance_name', 'slice', 'deploymentNetwork', 'flavor', 'image', 'node']
345 readonly_fields = ['backend_status_icon', 'all_ips_string', 'instance_name']
346 suit_classes = 'suit-tab suit-tab-slivers'
348 def queryset(self, request):
349 return Sliver.select_by_user(request.user)
351 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
352 if db_field.name == 'deploymentNetwork':
353 kwargs['queryset'] = Deployment.select_by_acl(request.user)
354 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_deployment_changed(this);"})
355 elif db_field.name == 'flavor':
356 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_flavor_changed(this);"})
358 field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
362 class SiteInline(PlStackTabularInline):
365 suit_classes = 'suit-tab suit-tab-sites'
367 def queryset(self, request):
368 return Site.select_by_user(request.user)
370 class UserInline(PlStackTabularInline):
372 fields = ['backend_status_icon', 'email', 'firstname', 'lastname']
373 readonly_fields = ('backend_status_icon', )
375 suit_classes = 'suit-tab suit-tab-users'
377 def queryset(self, request):
378 return User.select_by_user(request.user)
380 class SliceInline(PlStackTabularInline):
382 fields = ['backend_status_icon', 'name', 'site', 'serviceClass', 'service']
383 readonly_fields = ('backend_status_icon', )
385 suit_classes = 'suit-tab suit-tab-slices'
387 def queryset(self, request):
388 return Slice.select_by_user(request.user)
390 class NodeInline(PlStackTabularInline):
393 suit_classes = 'suit-tab suit-tab-nodes'
394 fields = ['backend_status_icon', 'name','deployment','site']
395 readonly_fields = ('backend_status_icon', )
397 class ControllerPrivilegeInline(PlStackTabularInline):
398 model = ControllerPrivilege
400 suit_classes = 'suit-tab suit-tab-admin-only'
401 fields = ['backend_status_icon', 'user','role','controller']
402 readonly_fields = ('backend_status_icon', )
404 def queryset(self, request):
405 return ControllerPrivilege.select_by_user(request.user)
407 class SitePrivilegeInline(PlStackTabularInline):
408 model = SitePrivilege
410 suit_classes = 'suit-tab suit-tab-siteprivileges'
411 fields = ['backend_status_icon', 'user','site', 'role']
412 readonly_fields = ('backend_status_icon', )
414 def formfield_for_foreignkey(self, db_field, request, **kwargs):
415 if db_field.name == 'site':
416 kwargs['queryset'] = Site.select_by_user(request.user)
418 if db_field.name == 'user':
419 kwargs['queryset'] = User.select_by_user(request.user)
420 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
422 def queryset(self, request):
423 return SitePrivilege.select_by_user(request.user)
425 class SiteDeploymentsInline(PlStackTabularInline):
426 model = SiteDeployments
428 suit_classes = 'suit-tab suit-tab-deployments'
429 fields = ['backend_status_icon', 'deployment','site', 'controller']
430 readonly_fields = ('backend_status_icon', )
432 def formfield_for_foreignkey(self, db_field, request, **kwargs):
433 if db_field.name == 'site':
434 kwargs['queryset'] = Site.select_by_user(request.user)
436 if db_field.name == 'deployment':
437 kwargs['queryset'] = Deployment.select_by_user(request.user)
439 if db_field.name == 'controller':
440 kwargs['queryset'] = Controller.select_by_user(request.user)
442 return super(SiteDeploymentsInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
444 def queryset(self, request):
445 return SiteDeployments.select_by_user(request.user)
448 class SlicePrivilegeInline(PlStackTabularInline):
449 model = SlicePrivilege
450 suit_classes = 'suit-tab suit-tab-sliceprivileges'
452 fields = ('backend_status_icon', 'user', 'slice', 'role')
453 readonly_fields = ('backend_status_icon', )
455 def formfield_for_foreignkey(self, db_field, request, **kwargs):
456 if db_field.name == 'slice':
457 kwargs['queryset'] = Slice.select_by_user(request.user)
458 if db_field.name == 'user':
459 kwargs['queryset'] = User.select_by_user(request.user)
461 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
463 def queryset(self, request):
464 return SlicePrivilege.select_by_user(request.user)
466 class SliceNetworkInline(PlStackTabularInline):
467 model = Network.slices.through
468 selflink_fieldname = "network"
470 verbose_name = "Network Connection"
471 verbose_name_plural = "Network Connections"
472 suit_classes = 'suit-tab suit-tab-slicenetworks'
473 fields = ['backend_status_icon', 'network']
474 readonly_fields = ('backend_status_icon', )
476 class ImageDeploymentsInline(PlStackTabularInline):
477 model = ImageDeployments
479 verbose_name = "Image Deployments"
480 verbose_name_plural = "Image Deployments"
481 suit_classes = 'suit-tab suit-tab-imagedeployments'
482 fields = ['backend_status_icon', 'image', 'deployment']
483 readonly_fields = ['backend_status_icon']
485 class ControllerImagesInline(PlStackTabularInline):
486 model = ControllerImages
488 verbose_name = "Controller Images"
489 verbose_name_plural = "Controller Images"
490 suit_classes = 'suit-tab suit-tab-admin-only'
491 fields = ['backend_status_icon', 'image', 'controller', 'glance_image_id']
492 readonly_fields = ['backend_status_icon', 'glance_image_id']
494 class SliceRoleAdmin(PlanetStackBaseAdmin):
498 class SiteRoleAdmin(PlanetStackBaseAdmin):
502 class DeploymentAdminForm(forms.ModelForm):
503 sites = forms.ModelMultipleChoiceField(
504 queryset=Site.objects.all(),
506 help_text="Select which sites are allowed to host nodes in this deployment",
507 widget=FilteredSelectMultiple(
508 verbose_name=('Sites'), is_stacked=False
511 images = forms.ModelMultipleChoiceField(
512 queryset=Image.objects.all(),
514 help_text="Select which images should be deployed on this deployment",
515 widget=FilteredSelectMultiple(
516 verbose_name=('Images'), is_stacked=False
519 flavors = forms.ModelMultipleChoiceField(
520 queryset=Flavor.objects.all(),
522 help_text="Select which flavors should be usable on this deployment",
523 widget=FilteredSelectMultiple(
524 verbose_name=('Flavors'), is_stacked=False
529 many_to_many = ["flavors",]
531 def __init__(self, *args, **kwargs):
532 request = kwargs.pop('request', None)
533 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
535 self.fields['accessControl'].initial = "allow site " + request.user.site.name
537 if self.instance and self.instance.pk:
538 self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments.all()]
539 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments.all()]
540 self.fields['flavors'].initial = self.instance.flavors.all()
542 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
543 """ helper function for handling m2m relations from the MultipleChoiceField
545 this_obj: the source object we want to link from
547 selected_objs: a list of destination objects we want to link to
549 all_relations: the full set of relations involving this_obj, including ones we don't want
551 relation_class: the class that implements the relation from source to dest
553 local_attrname: field name representing this_obj in relation_class
555 foreign_attrname: field name representing selected_objs in relation_class
557 This function will remove all newobjclass relations from this_obj
558 that are not contained in selected_objs, and add any relations that
559 are in selected_objs but don't exist in the data model yet.
562 existing_dest_objs = []
563 for relation in list(all_relations):
564 if getattr(relation, foreign_attrname) not in selected_objs:
565 #print "deleting site", sdp.site
568 existing_dest_objs.append(getattr(relation, foreign_attrname))
570 for dest_obj in selected_objs:
571 if dest_obj not in existing_dest_objs:
572 #print "adding site", site
573 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
574 relation = relation_class(**kwargs)
577 def save(self, commit=True):
578 deployment = super(DeploymentAdminForm, self).save(commit=False)
582 # this has to be done after save() if/when a deployment is first created
583 deployment.flavors = self.cleaned_data['flavors']
586 # save_m2m() doesn't seem to work with 'through' relations. So we
587 # create/destroy the through models ourselves. There has to be
590 self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments.all(), SiteDeployments, "deployment", "site")
591 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments.all(), ControllerImages, "deployment", "image")
597 class DeploymentAdminROForm(DeploymentAdminForm):
598 def save(self, commit=True):
599 raise PermissionDenied
601 class SiteAssocInline(PlStackTabularInline):
602 model = Site.deployments.through
604 suit_classes = 'suit-tab suit-tab-sites'
606 class DeploymentAdmin(PlanetStackBaseAdmin):
608 fieldList = ['backend_status_text', 'name', 'availability_zone', 'sites', 'images', 'flavors', 'accessControl']
609 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
610 inlines = [ControllerPrivilegeInline,NodeInline,TagInline] # ,ControllerImagesInline]
611 list_display = ['backend_status_icon', 'name']
612 list_display_links = ('backend_status_icon', 'name', )
613 readonly_fields = ('backend_status_text', )
615 user_readonly_fields = ['name']
617 suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags')) # ,('imagedeployments','Images'))
619 def get_form(self, request, obj=None, **kwargs):
620 if request.user.isReadOnlyUser():
621 kwargs["form"] = DeploymentAdminROForm
623 kwargs["form"] = DeploymentAdminForm
624 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
626 # from stackexchange: pass the request object into the form
628 class AdminFormMetaClass(adminForm):
629 def __new__(cls, *args, **kwargs):
630 kwargs['request'] = request
631 return adminForm(*args, **kwargs)
633 return AdminFormMetaClass
635 class ControllerAdminForm(forms.ModelForm):
636 site_deployments = forms.ModelMultipleChoiceField(
637 queryset=SiteDeployment.objects.all(),
639 help_text="Select which sites deployments are managed by this controller",
640 widget=FilteredSelectMultiple(
641 verbose_name=('Site Deployments'), is_stacked=False
648 def __init__(self, *args, **kwds):
649 request = kwargs.pop('request', None)
650 super(ControllerAdminForm, self).__init__(*args, **kwargs)
652 if self.instance and self.instance.pk:
653 self.fields['site_deployments'].initial = [x.site_deployment for x in self.instance.controllersitedeployments.all()]
655 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
656 """ helper function for handling m2m relations from the MultipleChoiceField
657 this_obj: the source object we want to link from
658 selected_objs: a list of destination objects we want to link to
659 all_relations: the full set of relations involving this_obj, including ones we don't want
660 relation_class: the class that implements the relation from source to dest
661 local_attrname: field name representing this_obj in relation_class
662 foreign_attrname: field name representing selected_objs in relation_class
663 This function will remove all newobjclass relations from this_obj
664 that are not contained in selected_objs, and add any relations that
665 are in selected_objs but don't exist in the data model yet.
667 existing_dest_objs = []
668 for relation in list(all_relations):
669 if getattr(relation, foreign_attrname) not in selected_objs:
670 #print "deleting site", sdp.site
673 existing_dest_objs.append(getattr(relation, foreign_attrname))
675 for dest_obj in selected_objs:
676 if dest_obj not in existing_dest_objs:
677 #print "adding site", site
678 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
679 relation = relation_class(**kwargs)
682 def save(self, commit=True):
683 controller = super(ControllerAdminForm, self).save(commit=False)
689 # save_m2m() doesn't seem to work with 'through' relations. So we
690 # create/destroy the through models ourselves. There has to be
692 self.manipulate_m2m_objs(controller, self.cleaned_data['site_deployments'], controller.controllersitedeployments.all(), ControllerSiteDeployments, "controller", "site_deployment")
698 class ControllerAdmin(PlanetStackBaseAdmin):
700 fieldList = ['name', 'version', 'backend_type', 'auth_url', 'admin_user', 'admin_tenant',]
701 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-admin-only']})]
702 inlines = [ControllerPrivilegeInline, ContrllerSiteInline] # ,ControllerImagesInline]
703 list_display = ['backend_status_icon', 'name', 'version', 'backend_type']
704 list_display_links = ('backend_status_icon', 'name', )
705 readonly_fields = ('backend_status_text',)
707 user_readonly_fields = []
709 def get_form(self, request, obj=None, **kwargs):
710 if request.user.isReadOnlyUser():
711 kwargs["form"] = ControllerAdminROForm
713 kwargs["form"] = ControllerAdminForm
714 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
716 # from stackexchange: pass the request object into the form
718 class AdminFormMetaClass(adminForm):
719 def __new__(cls, *args, **kwargs):
720 kwargs['request'] = request
721 return adminForm(*args, **kwargs)
723 return AdminFormMetaClass
725 class ServiceAttrAsTabInline(PlStackTabularInline):
726 model = ServiceAttribute
727 fields = ['name','value']
729 suit_classes = 'suit-tab suit-tab-serviceattrs'
731 class ServiceAdmin(PlanetStackBaseAdmin):
732 list_display = ("backend_status_icon","name","description","versionNumber","enabled","published")
733 list_display_links = ('backend_status_icon', 'name', )
734 fieldList = ["backend_status_text","name","description","versionNumber","enabled","published"]
735 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
736 inlines = [ServiceAttrAsTabInline,SliceInline]
737 readonly_fields = ('backend_status_text', )
739 user_readonly_fields = fieldList
741 suit_form_tabs =(('general', 'Service Details'),
743 ('serviceattrs','Additional Attributes'),
746 class SiteAdmin(PlanetStackBaseAdmin):
747 fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
749 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
750 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
752 suit_form_tabs =(('general', 'Site Details'),
754 ('siteprivileges','Privileges'),
755 ('deployments','Deployments'),
760 readonly_fields = ['backend_status_text', 'accountLink']
762 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
764 list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
765 list_display_links = ('backend_status_icon', 'name', )
766 filter_horizontal = ('deployments',)
767 inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentsInline]
768 search_fields = ['name']
770 def queryset(self, request):
771 return Site.select_by_user(request.user)
773 def get_formsets(self, request, obj=None):
774 for inline in self.get_inline_instances(request, obj):
775 # hide MyInline in the add view
778 if isinstance(inline, SliverInline):
779 inline.model.caller = request.user
780 yield inline.get_formset(request, obj)
782 def accountLink(self, obj):
783 link_obj = obj.accounts.all()
785 reverse_path = "admin:core_account_change"
786 url = reverse(reverse_path, args =(link_obj[0].id,))
787 return "<a href='%s'>%s</a>" % (url, "view billing details")
789 return "no billing data for this site"
790 accountLink.allow_tags = True
791 accountLink.short_description = "Billing"
793 def save_model(self, request, obj, form, change):
794 # update openstack connection to use this site/tenant
795 obj.save_by_user(request.user)
797 def delete_model(self, request, obj):
798 obj.delete_by_user(request.user)
801 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
802 fieldList = ['backend_status_text', 'user', 'site', 'role']
804 (None, {'fields': fieldList, 'classes':['collapse']})
806 readonly_fields = ('backend_status_text', )
807 list_display = ('backend_status_icon', 'user', 'site', 'role')
808 list_display_links = list_display
809 user_readonly_fields = fieldList
810 user_readonly_inlines = []
812 def formfield_for_foreignkey(self, db_field, request, **kwargs):
813 if db_field.name == 'site':
814 if not request.user.is_admin:
815 # only show sites where user is an admin or pi
817 for site_privilege in SitePrivilege.objects.filer(user=request.user):
818 if site_privilege.role.role_type in ['admin', 'pi']:
819 sites.add(site_privilege.site)
820 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
822 if db_field.name == 'user':
823 if not request.user.is_admin:
824 # only show users from sites where caller has admin or pi role
825 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
826 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
827 sites = [site_privilege.site for site_privilege in site_privileges]
828 site_privileges = SitePrivilege.objects.filter(site__in=sites)
829 emails = [site_privilege.user.email for site_privilege in site_privileges]
830 users = User.objects.filter(email__in=emails)
831 kwargs['queryset'] = users
833 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
835 def queryset(self, request):
836 # admins can see all privileges. Users can only see privileges at sites
837 # where they have the admin role or pi role.
838 qs = super(SitePrivilegeAdmin, self).queryset(request)
839 #if not request.user.is_admin:
840 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
841 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
842 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
843 # sites = Site.objects.filter(login_base__in=login_bases)
844 # qs = qs.filter(site__in=sites)
847 class SliceForm(forms.ModelForm):
851 'service': LinkedSelect
855 cleaned_data = super(SliceForm, self).clean()
856 name = cleaned_data.get('name')
857 site = cleaned_data.get('site')
858 slice_id = self.instance.id
859 if not site and slice_id:
860 site = Slice.objects.get(id=slice_id).site
861 if (not isinstance(site,Site)):
862 # previous code indicates 'site' could be a site_id and not a site?
863 site = Slice.objects.get(id=site.id)
864 if not name.startswith(site.login_base):
865 raise forms.ValidationError('slice name must begin with %s' % site.login_base)
868 class ControllerSlicesInline(PlStackTabularInline):
869 model = ControllerSlices
871 verbose_name = "Controller Slices"
872 verbose_name_plural = "Controller Slices"
873 suit_classes = 'suit-tab suit-tab-admin-only'
874 fields = ['backend_status_icon', 'controller', 'tenant_id']
875 readonly_fields = ('backend_status_icon', )
877 class SliceAdmin(PlanetStackBaseAdmin):
879 fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
880 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
881 readonly_fields = ('backend_status_text', )
882 list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_slivers')
883 list_display_links = ('backend_status_icon', 'name', )
884 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
885 admin_inlines = [ControllerSlicesInline]
887 user_readonly_fields = fieldList
890 def suit_form_tabs(self):
891 tabs =[('general', 'Slice Details'),
892 ('slicenetworks','Networks'),
893 ('sliceprivileges','Privileges'),
894 ('slivers','Slivers'),
896 ('reservations','Reservations'),
899 request=getattr(_thread_locals, "request", None)
900 if request and request.user.is_admin:
901 tabs.append( ('admin-only', 'Admin-Only') )
905 def add_view(self, request, form_url='', extra_context=None):
906 # revert to default read-only fields
907 self.readonly_fields = ('backend_status_text',)
908 return super(SliceAdmin, self).add_view(request, form_url, extra_context=extra_context)
910 def change_view(self, request, object_id, form_url='', extra_context=None):
912 # cannot change the site of an existing slice so make the site field read only
914 self.readonly_fields = ('backend_status_text','site')
915 return super(SliceAdmin, self).change_view(request, object_id, form_url)
917 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
918 deployment_nodes = []
919 for node in Node.objects.all():
920 deployment_nodes.append( (node.deployment.id, node.id, node.name) )
922 deployment_flavors = []
923 for flavor in Flavor.objects.all():
924 for deployment in flavor.deployments.all():
925 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
927 controller_images = []
928 for image in Image.objects.all():
929 for controller_image in image.controllerimages.all():
930 controller_images.append( (controller_image.controller.id, image.id, image.name) )
932 site_login_bases = []
933 for site in Site.objects.all():
934 site_login_bases.append((site.id, site.login_base))
936 context["deployment_nodes"] = deployment_nodes
937 context["deployment_flavors"] = deployment_flavors
938 context["deployment_images"] = deployment_images
939 context["site_login_bases"] = site_login_bases
940 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
942 def formfield_for_foreignkey(self, db_field, request, **kwargs):
943 if db_field.name == 'site':
944 kwargs['queryset'] = Site.select_by_user(request.user)
945 kwargs['widget'] = forms.Select(attrs={'onChange': "update_slice_prefix(this, $($(this).closest('fieldset')[0]).find('.field-name input')[0].id)"})
947 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
949 def queryset(self, request):
950 # admins can see all keys. Users can only see slices they belong to.
951 return Slice.select_by_user(request.user)
953 def get_formsets(self, request, obj=None):
954 for inline in self.get_inline_instances(request, obj):
955 # hide MyInline in the add view
958 if isinstance(inline, SliverInline):
959 inline.model.caller = request.user
960 yield inline.get_formset(request, obj)
962 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
964 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
966 readonly_fields = ('backend_status_text', )
967 list_display = ('backend_status_icon', 'user', 'slice', 'role')
968 list_display_links = list_display
970 user_readonly_fields = ['user', 'slice', 'role']
971 user_readonly_inlines = []
973 def formfield_for_foreignkey(self, db_field, request, **kwargs):
974 if db_field.name == 'slice':
975 kwargs['queryset'] = Slice.select_by_user(request.user)
977 if db_field.name == 'user':
978 kwargs['queryset'] = User.select_by_user(request.user)
980 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
982 def queryset(self, request):
983 # admins can see all memberships. Users can only see memberships of
984 # slices where they have the admin role.
985 return SlicePrivilege.select_by_user(request.user)
987 def save_model(self, request, obj, form, change):
988 # update openstack connection to use this site/tenant
989 auth = request.session.get('auth', {})
990 auth['tenant'] = obj.slice.slicename
991 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
994 def delete_model(self, request, obj):
995 # update openstack connection to use this site/tenant
996 auth = request.session.get('auth', {})
997 auth['tenant'] = obj.slice.slicename
998 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1002 class ImageAdmin(PlanetStackBaseAdmin):
1004 fieldsets = [('Image Details',
1005 {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
1006 'classes': ['suit-tab suit-tab-general']})
1008 readonly_fields = ('backend_status_text', )
1010 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'), ('controllerimages', 'Controllers'))
1012 inlines = [SliverInline, ControllerImagesInline]
1014 user_readonly_fields = ['name', 'disk_format', 'container_format']
1016 list_display = ['backend_status_icon', 'name']
1017 list_display_links = ('backend_status_icon', 'name', )
1019 class NodeForm(forms.ModelForm):
1022 'site': LinkedSelect,
1023 'deployment': LinkedSelect
1026 class NodeAdmin(PlanetStackBaseAdmin):
1028 list_display = ('backend_status_icon', 'name', 'site', 'deployment')
1029 list_display_links = ('backend_status_icon', 'name', )
1030 list_filter = ('deployment',)
1032 inlines = [TagInline,SliverInline]
1033 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
1034 readonly_fields = ('backend_status_text', )
1036 user_readonly_fields = ['name','site','deployment']
1037 user_readonly_inlines = [TagInline,SliverInline]
1039 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
1042 class SliverForm(forms.ModelForm):
1045 ip = forms.CharField(widget=PlainTextWidget)
1046 instance_name = forms.CharField(widget=PlainTextWidget)
1048 'ip': PlainTextWidget(),
1049 'instance_name': PlainTextWidget(),
1050 'slice': LinkedSelect,
1051 'deploymentNetwork': LinkedSelect,
1052 'node': LinkedSelect,
1053 'image': LinkedSelect
1056 class TagAdmin(PlanetStackBaseAdmin):
1057 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
1058 list_display_links = list_display
1059 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
1060 user_readonly_inlines = []
1062 class SliverAdmin(PlanetStackBaseAdmin):
1065 ('Sliver Details', {'fields': ['backend_status_text', 'slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
1067 readonly_fields = ('backend_status_text', )
1068 list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'flavor', 'image', 'node', 'deploymentNetwork']
1069 list_display_links = ('backend_status_icon', 'ip',)
1071 suit_form_tabs =(('general', 'Sliver Details'),
1075 inlines = [TagInline]
1077 user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image']
1079 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1080 if db_field.name == 'slice':
1081 kwargs['queryset'] = Slice.select_by_user(request.user)
1083 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1085 def queryset(self, request):
1086 # admins can see all slivers. Users can only see slivers of
1087 # the slices they belong to.
1088 return Sliver.select_by_user(request.user)
1091 def get_formsets(self, request, obj=None):
1092 # make some fields read only if we are updating an existing record
1094 #self.readonly_fields = ('ip', 'instance_name')
1095 self.readonly_fields = ('backend_status_text',)
1097 self.readonly_fields = ('backend_status_text',)
1098 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
1100 for inline in self.get_inline_instances(request, obj):
1101 # hide MyInline in the add view
1104 if isinstance(inline, SliverInline):
1105 inline.model.caller = request.user
1106 yield inline.get_formset(request, obj)
1108 #def save_model(self, request, obj, form, change):
1109 # # update openstack connection to use this site/tenant
1110 # auth = request.session.get('auth', {})
1111 # auth['tenant'] = obj.slice.name
1112 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1113 # obj.creator = request.user
1116 #def delete_model(self, request, obj):
1117 # # update openstack connection to use this site/tenant
1118 # auth = request.session.get('auth', {})
1119 # auth['tenant'] = obj.slice.name
1120 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1123 class UserCreationForm(forms.ModelForm):
1124 """A form for creating new users. Includes all the required
1125 fields, plus a repeated password."""
1126 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
1127 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
1131 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
1133 def clean_password2(self):
1134 # Check that the two password entries match
1135 password1 = self.cleaned_data.get("password1")
1136 password2 = self.cleaned_data.get("password2")
1137 if password1 and password2 and password1 != password2:
1138 raise forms.ValidationError("Passwords don't match")
1141 def save(self, commit=True):
1142 # Save the provided password in hashed format
1143 user = super(UserCreationForm, self).save(commit=False)
1144 user.password = self.cleaned_data["password1"]
1145 #user.set_password(self.cleaned_data["password1"])
1151 class UserChangeForm(forms.ModelForm):
1152 """A form for updating users. Includes all the fields on
1153 the user, but replaces the password field with admin's
1154 password hash display field.
1156 password = ReadOnlyPasswordHashField(label='Password',
1157 help_text= '<a href=\"password/\">Change Password</a>.')
1161 widgets = { 'public_key': UploadTextareaWidget, }
1163 def clean_password(self):
1164 # Regardless of what the user provides, return the initial value.
1165 # This is done here, rather than on the field, because the
1166 # field does not have access to the initial value
1167 return self.initial["password"]
1169 class UserDashboardViewInline(PlStackTabularInline):
1170 model = UserDashboardView
1172 suit_classes = 'suit-tab suit-tab-dashboards'
1173 fields = ['user', 'dashboardView', 'order']
1175 class UserAdmin(PermissionCheckingAdminMixin, UserAdmin):
1176 # Note: Make sure PermissionCheckingAdminMixin is listed before
1177 # admin.ModelAdmin in the class declaration.
1182 # The forms to add and change user instances
1183 form = UserChangeForm
1184 add_form = UserCreationForm
1186 # The fields to be used in displaying the User model.
1187 # These override the definitions on the base UserAdmin
1188 # that reference specific fields on auth.User.
1189 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
1190 list_filter = ('site',)
1191 inlines = [SlicePrivilegeInline,SitePrivilegeInline,ControllerPrivilegeInline,UserDashboardViewInline]
1193 fieldListLoginDetails = ['backend_status_text', 'email','site','password','is_active','is_readonly','is_admin','public_key']
1194 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1197 ('Login Details', {'fields': ['backend_status_text', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1198 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1199 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1200 #('Important dates', {'fields': ('last_login',)}),
1204 'classes': ('wide',),
1205 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')},
1208 readonly_fields = ('backend_status_text', )
1209 search_fields = ('email',)
1210 ordering = ('email',)
1211 filter_horizontal = ()
1213 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1216 def suit_form_tabs(self):
1217 if getattr(_thread_locals, "obj", None) is None:
1220 return (('general','Login Details'),
1221 ('contact','Contact Information'),
1222 ('sliceprivileges','Slice Privileges'),
1223 ('siteprivileges','Site Privileges'),
1224 ('controllerprivileges','Controller Privileges'),
1225 ('dashboards','Dashboard Views'))
1227 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1228 if db_field.name == 'site':
1229 kwargs['queryset'] = Site.select_by_user(request.user)
1231 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1233 def queryset(self, request):
1234 return User.select_by_user(request.user)
1236 class DashboardViewAdmin(PlanetStackBaseAdmin):
1237 fieldsets = [('Dashboard View Details',
1238 {'fields': ['backend_status_text', 'name', 'url'],
1239 'classes': ['suit-tab suit-tab-general']})
1241 readonly_fields = ('backend_status_text', )
1243 suit_form_tabs =(('general','Dashboard View Details'),)
1245 class ServiceResourceInline(PlStackTabularInline):
1246 model = ServiceResource
1249 class ServiceClassAdmin(PlanetStackBaseAdmin):
1250 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1251 list_display_links = ('backend_status_icon', 'name', )
1252 inlines = [ServiceResourceInline]
1254 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1255 user_readonly_inlines = []
1257 class ReservedResourceInline(PlStackTabularInline):
1258 model = ReservedResource
1260 suit_classes = 'suit-tab suit-tab-reservedresources'
1262 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1263 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1265 if db_field.name == 'resource':
1266 # restrict resources to those that the slice's service class allows
1267 if request._slice is not None:
1268 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1269 if len(field.queryset) > 0:
1270 field.initial = field.queryset.all()[0]
1272 field.queryset = field.queryset.none()
\r
1273 elif db_field.name == 'sliver':
\r
1274 # restrict slivers to those that belong to the slice
\r
1275 if request._slice is not None:
\r
1276 field.queryset = field.queryset.filter(slice = request._slice)
1278 field.queryset = field.queryset.none()
\r
1282 def queryset(self, request):
1283 return ReservedResource.select_by_user(request.user)
1285 class ReservationChangeForm(forms.ModelForm):
1289 'slice' : LinkedSelect
1292 class ReservationAddForm(forms.ModelForm):
1293 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1294 refresh = forms.CharField(widget=forms.HiddenInput())
1297 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1299 def clean_slice(self):
1300 slice = self.cleaned_data.get("slice")
1301 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1303 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1309 'slice' : LinkedSelect
1313 class ReservationAddRefreshForm(ReservationAddForm):
1314 """ This form is displayed when the Reservation Form receives an update
1315 from the Slice dropdown onChange handler. It doesn't validate the
1316 data and doesn't save the data. This will cause the form to be
1320 """ don't validate anything other than slice """
1321 dont_validate_fields = ("startTime", "duration")
1323 def full_clean(self):
1324 result = super(ReservationAddForm, self).full_clean()
1326 for fieldname in self.dont_validate_fields:
1327 if fieldname in self._errors:
1328 del self._errors[fieldname]
1332 """ don't save anything """
1336 class ReservationAdmin(PlanetStackBaseAdmin):
1337 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
1338 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1339 readonly_fields = ('backend_status_text', )
1340 list_display = ('startTime', 'duration')
1341 form = ReservationAddForm
1343 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1345 inlines = [ReservedResourceInline]
1346 user_readonly_fields = fieldList
1348 def add_view(self, request, form_url='', extra_context=None):
1349 timezone.activate(request.user.timezone)
1350 request._refresh = False
1351 request._slice = None
1352 if request.method == 'POST':
1353 # "refresh" will be set to "1" if the form was submitted due to
1354 # a change in the Slice dropdown.
1355 if request.POST.get("refresh","1") == "1":
1356 request._refresh = True
1357 request.POST["refresh"] = "0"
1359 # Keep track of the slice that was selected, so the
1360 # reservedResource inline can filter items for the slice.
1361 request._slice = request.POST.get("slice",None)
1362 if (request._slice is not None):
1363 request._slice = Slice.objects.get(id=request._slice)
1365 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1368 def changelist_view(self, request, extra_context = None):
1369 timezone.activate(request.user.timezone)
1370 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1372 def get_form(self, request, obj=None, **kwargs):
1375 # For changes, set request._slice to the slice already set in the
1377 request._slice = obj.slice
1378 self.form = ReservationChangeForm
1380 if getattr(request, "_refresh", False):
1381 self.form = ReservationAddRefreshForm
1383 self.form = ReservationAddForm
1384 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1386 def get_readonly_fields(self, request, obj=None):
1387 if (obj is not None):
1388 # Prevent slice from being changed after the reservation has been
1394 def queryset(self, request):
1395 return Reservation.select_by_user(request.user)
1397 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1398 list_display = ("backend_status_icon", "name", )
1399 list_display_links = ('backend_status_icon', 'name', )
1400 user_readonly_fields = ['name']
1401 user_readonly_inlines = []
1403 class RouterAdmin(PlanetStackBaseAdmin):
1404 list_display = ("backend_status_icon", "name", )
1405 list_display_links = ('backend_status_icon', 'name', )
1406 user_readonly_fields = ['name']
1407 user_readonly_inlines = []
1409 class RouterInline(PlStackTabularInline):
1410 model = Router.networks.through
1412 verbose_name_plural = "Routers"
1413 verbose_name = "Router"
1414 suit_classes = 'suit-tab suit-tab-routers'
1416 class NetworkParameterInline(PlStackGenericTabularInline):
1417 model = NetworkParameter
1419 verbose_name_plural = "Parameters"
1420 verbose_name = "Parameter"
1421 suit_classes = 'suit-tab suit-tab-netparams'
1422 fields = ['backend_status_icon', 'parameter', 'value']
1423 readonly_fields = ('backend_status_icon', )
1425 class NetworkSliversInline(PlStackTabularInline):
1426 fields = ['backend_status_icon', 'network','sliver','ip']
1427 readonly_fields = ("backend_status_icon", "ip", )
1428 model = NetworkSliver
1429 selflink_fieldname = "sliver"
1431 verbose_name_plural = "Slivers"
1432 verbose_name = "Sliver"
1433 suit_classes = 'suit-tab suit-tab-networkslivers'
1435 class NetworkSlicesInline(PlStackTabularInline):
1436 model = NetworkSlice
1437 selflink_fieldname = "slice"
1439 verbose_name_plural = "Slices"
1440 verbose_name = "Slice"
1441 suit_classes = 'suit-tab suit-tab-networkslices'
1442 fields = ['backend_status_icon', 'network','slice']
1443 readonly_fields = ('backend_status_icon', )
1445 class ControllerNetworksInline(PlStackTabularInline):
1446 model = ControllerNetworks
1448 verbose_name_plural = "Controller Networks"
1449 verbose_name = "Controller Network"
1450 suit_classes = 'suit-tab suit-tab-admin-only'
1451 fields = ['backend_status_icon', 'controller','net_id','subnet_id']
1452 readonly_fields = ('backend_status_icon', )
1454 class NetworkForm(forms.ModelForm):
1458 'topologyParameters': UploadTextareaWidget,
1459 'controllerParameters': UploadTextareaWidget,
1462 class NetworkAdmin(PlanetStackBaseAdmin):
1463 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1464 list_display_links = ('backend_status_icon', 'name', )
1465 readonly_fields = ("subnet", )
1467 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1468 admin_inlines = [ControllerNetworksInline]
1473 (None, {'fields': ['backend_status_text', 'name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet'],
1474 'classes':['suit-tab suit-tab-general']}),
1475 (None, {'fields': ['topologyParameters', 'controllerUrl', 'controllerParameters'],
1476 'classes':['suit-tab suit-tab-sdn']}),
1479 readonly_fields = ('backend_status_text', )
1480 user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1483 def suit_form_tabs(self):
1484 tabs=[('general','Network Details'),
1485 ('sdn', 'SDN Configuration'),
1486 ('netparams', 'Parameters'),
1487 ('networkslivers','Slivers'),
1488 ('networkslices','Slices'),
1489 ('routers','Routers'),
1492 request=getattr(_thread_locals, "request", None)
1493 if request and request.user.is_admin:
1494 tabs.append( ('admin-only', 'Admin-Only') )
1499 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1500 list_display = ("backend_status_icon", "name", "guaranteedBandwidth", "visibility")
1501 list_display_links = ('backend_status_icon', 'name', )
1502 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1503 user_readonly_inlines = []
1505 (None, {'fields': ['name', 'description', 'guaranteedBandwidth', 'visibility', 'translation', 'sharedNetworkName', 'sharedNetworkId', 'topologyKind', 'controllerKind'],
1506 'classes':['suit-tab suit-tab-general']}),]
1507 suit_form_tabs = (('general','Network Template Details'), )
1509 class FlavorAdmin(PlanetStackBaseAdmin):
1510 list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1511 list_display_links = ("backend_status_icon", "name")
1512 user_readonly_fields = ("name", "flavor")
1513 fields = ("name", "description", "flavor", "order", "default")
1515 # register a signal that caches the user's credentials when they log in
1516 def cache_credentials(sender, user, request, **kwds):
1517 auth = {'username': request.POST['username'],
1518 'password': request.POST['password']}
1519 request.session['auth'] = auth
1520 user_logged_in.connect(cache_credentials)
1522 def dollar_field(fieldName, short_description):
1523 def newFunc(self, obj):
1525 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1527 x=getattr(obj, fieldName, 0.0)
1529 newFunc.short_description = short_description
1532 def right_dollar_field(fieldName, short_description):
1533 def newFunc(self, obj):
1535 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1536 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1538 x=getattr(obj, fieldName, 0.0)
1540 newFunc.short_description = short_description
1541 newFunc.allow_tags = True
1544 class InvoiceChargeInline(PlStackTabularInline):
1547 verbose_name_plural = "Charges"
1548 verbose_name = "Charge"
1549 exclude = ['account']
1550 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1551 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1555 dollar_amount = right_dollar_field("amount", "Amount")
1557 class InvoiceAdmin(admin.ModelAdmin):
1558 list_display = ("date", "account")
1560 inlines = [InvoiceChargeInline]
1562 fields = ["date", "account", "dollar_amount"]
1563 readonly_fields = ["date", "account", "dollar_amount"]
1565 dollar_amount = dollar_field("amount", "Amount")
1567 class InvoiceInline(PlStackTabularInline):
1570 verbose_name_plural = "Invoices"
1571 verbose_name = "Invoice"
1572 fields = ["date", "dollar_amount"]
1573 readonly_fields = ["date", "dollar_amount"]
1574 suit_classes = 'suit-tab suit-tab-accountinvoice'
1578 dollar_amount = right_dollar_field("amount", "Amount")
1580 class PendingChargeInline(PlStackTabularInline):
1583 verbose_name_plural = "Charges"
1584 verbose_name = "Charge"
1585 exclude = ["invoice"]
1586 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1587 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1588 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1592 def queryset(self, request):
1593 qs = super(PendingChargeInline, self).queryset(request)
1594 qs = qs.filter(state="pending")
1597 dollar_amount = right_dollar_field("amount", "Amount")
1599 class PaymentInline(PlStackTabularInline):
1602 verbose_name_plural = "Payments"
1603 verbose_name = "Payment"
1604 fields = ["date", "dollar_amount"]
1605 readonly_fields = ["date", "dollar_amount"]
1606 suit_classes = 'suit-tab suit-tab-accountpayments'
1610 dollar_amount = right_dollar_field("amount", "Amount")
1612 class AccountAdmin(admin.ModelAdmin):
1613 list_display = ("site", "balance_due")
1615 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1618 (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1620 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1623 ('general','Account Details'),
1624 ('accountinvoice', 'Invoices'),
1625 ('accountpayments', 'Payments'),
1626 ('accountpendingcharges','Pending Charges'),
1629 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1630 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1631 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1633 # Now register the new UserAdmin...
1634 admin.site.register(User, UserAdmin)
1635 # ... and, since we're not using Django's builtin permissions,
1636 # unregister the Group model from admin.
1637 #admin.site.unregister(Group)
1639 #Do not show django evolution in the admin interface
1640 from django_evolution.models import Version, Evolution
1641 #admin.site.unregister(Version)
1642 #admin.site.unregister(Evolution)
1645 # When debugging it is often easier to see all the classes, but for regular use
1646 # only the top-levels should be displayed
1649 admin.site.register(Deployment, DeploymentAdmin)
1650 admin.site.register(Controller, ControllerAdmin)
1651 admin.site.register(Site, SiteAdmin)
1652 admin.site.register(Slice, SliceAdmin)
1653 admin.site.register(Service, ServiceAdmin)
1654 admin.site.register(Reservation, ReservationAdmin)
1655 admin.site.register(Network, NetworkAdmin)
1656 admin.site.register(Router, RouterAdmin)
1657 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1658 admin.site.register(Account, AccountAdmin)
1659 admin.site.register(Invoice, InvoiceAdmin)
1662 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1663 admin.site.register(ServiceClass, ServiceClassAdmin)
1664 #admin.site.register(PlanetStack)
1665 admin.site.register(Tag, TagAdmin)
1666 admin.site.register(ControllerRole)
1667 admin.site.register(SiteRole)
1668 admin.site.register(SliceRole)
1669 admin.site.register(PlanetStackRole)
1670 admin.site.register(Node, NodeAdmin)
1671 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1672 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1673 admin.site.register(Sliver, SliverAdmin)
1674 admin.site.register(Image, ImageAdmin)
1675 admin.site.register(DashboardView, DashboardViewAdmin)
1676 admin.site.register(Flavor, FlavorAdmin)