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
11 from django.contrib.auth.forms import ReadOnlyPasswordHashField
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
19 import django_evolution
21 class ReadOnlyAwareAdmin(admin.ModelAdmin):
23 def has_add_permission(self, request, obj=None):
24 return (not self.__user_is_readonly(request))
26 def has_delete_permission(self, request, obj=None):
27 return (not self.__user_is_readonly(request))
29 def save_model(self, request, obj, form, change):
30 if self.__user_is_readonly(request):
31 raise PermissionDenied
34 return super(ReadOnlyAwareAdmin, self).save_model(request, obj, form, change)
36 def get_actions(self,request):
37 actions = super(ReadOnlyAwareAdmin,self).get_actions(request)
39 if self.__user_is_readonly(request):
40 if 'delete_selected' in actions:
41 del actions['delete_selected']
45 def change_view(self,request,object_id, extra_context=None):
46 if self.__user_is_readonly(request):
47 if not hasattr(self, "readonly_save"):
\r
48 # save the original readonly fields
\r
49 self.readonly_save = self.readonly_fields
\r
50 self.inlines_save = self.inlines
\r
51 if hasattr(self, "user_readonly_fields"):
\r
52 self.readonly_fields=self.user_readonly_fields
\r
53 if hasattr(self, "user_readonly_inlines"):
\r
54 self.inlines = self.user_readonly_inlines
\r
56 if hasattr(self, "readonly_save"):
\r
57 # restore the original readonly fields
\r
58 self.readonly_fields = self.readonly_save
\r
59 if hasattr(self, "inlines_save"):
\r
60 self.inlines = self.inlines_save
63 return super(ReadOnlyAwareAdmin, self).change_view(request, object_id, extra_context=extra_context)
64 except PermissionDenied:
66 if request.method == 'POST':
67 raise PermissionDenied
68 request.readonly = True
69 return super(ReadOnlyAwareAdmin, self).change_view(request, object_id, extra_context=extra_context)
71 def __user_is_readonly(self, request):
72 return request.user.isReadOnlyUser()
74 class SingletonAdmin (ReadOnlyAwareAdmin):
75 def has_add_permission(self, request):
76 if not super(SingletonAdmin, self).has_add_permission(request):
79 num_objects = self.model.objects.count()
86 class PlStackTabularInline(admin.TabularInline):
87 def __init__(self, *args, **kwargs):
88 super(PlStackTabularInline, self).__init__(*args, **kwargs)
90 # InlineModelAdmin as no get_fields() method, so in order to add
91 # the selflink field, we override __init__ to modify self.fields and
92 # self.readonly_fields.
96 def get_change_url(self, model, id):
97 """ Get the URL to a change form in the admin for this model """
98 reverse_path = "admin:%s_change" % (model._meta.db_table)
100 url = reverse(reverse_path, args=(id,))
101 except NoReverseMatch:
106 def setup_selflink(self):
107 if hasattr(self, "selflink_fieldname"):
108 """ self.selflink_model can be defined to punch through a relation
109 to its target object. For example, in SliceNetworkInline, set
110 selflink_model = "network", and the URL will lead to the Network
111 object instead of trying to bring up a change view of the
114 self.selflink_model = getattr(self.model,self.selflink_fieldname).field.rel.to
116 self.selflink_model = self.model
118 url = self.get_change_url(self.selflink_model, 0)
120 # We don't have an admin for this object, so don't create the
125 # Since we need to add "selflink" to the field list, we need to create
126 # self.fields if it is None.
127 if (self.fields is None):
129 for f in self.model._meta.fields:
130 if f.editable and f.name != "id":
131 self.fields.append(f.name)
133 self.fields = tuple(self.fields) + ("selflink", )
135 if self.readonly_fields is None:
136 self.readonly_fields = ()
138 self.readonly_fields = tuple(self.readonly_fields) + ("selflink", )
140 def selflink(self, obj):
141 if hasattr(self, "selflink_fieldname"):
142 obj = getattr(obj, self.selflink_fieldname)
145 url = self.get_change_url(self.selflink_model, obj.id)
146 return "<a href='%s'>Details</a>" % str(url)
148 return "Not present"
\r
150 selflink.allow_tags = True
151 selflink.short_description = "Details"
153 def has_add_permission(self, request):
154 return not request.user.isReadOnlyUser()
156 def get_readonly_fields(self, request, obj=None):
157 readonly_fields = list(self.readonly_fields)[:]
158 if request.user.isReadOnlyUser():
159 for field in self.fields:
160 if not field in readonly_fields:
161 readonly_fields.append(field)
162 return readonly_fields
164 class PlStackGenericTabularInline(generic.GenericTabularInline):
165 def has_add_permission(self, request):
166 return not request.user.isReadOnlyUser()
168 def get_readonly_fields(self, request, obj=None):
169 readonly_fields = list(self.readonly_fields)[:]
170 if request.user.isReadOnlyUser():
171 for field in self.fields:
172 if not field in readonly_fields:
173 readonly_fields.append(field)
174 return readonly_fields
176 class ReservationInline(PlStackTabularInline):
179 suit_classes = 'suit-tab suit-tab-reservations'
181 def queryset(self, request):
182 return Reservation.select_by_user(request.user)
184 class TagInline(PlStackGenericTabularInline):
187 suit_classes = 'suit-tab suit-tab-tags'
188 fields = ['service', 'name', 'value']
190 def queryset(self, request):
191 return Tag.select_by_user(request.user)
193 class NetworkLookerUpper:
194 """ This is a callable that looks up a network name in a sliver and returns
195 the ip address for that network.
198 byNetworkName = {} # class variable
200 def __init__(self, name):
201 self.short_description = name
203 self.network_name = name
205 def __call__(self, obj):
207 for nbs in obj.networksliver_set.all():
208 if (nbs.network.name == self.network_name):
213 return self.network_name
216 def get(network_name):
217 """ We want to make sure we alwars return the same NetworkLookerUpper
218 because sometimes django will cause them to be instantiated multiple
219 times (and we don't want different ones in form.fields vs
220 SliverInline.readonly_fields).
222 if network_name not in NetworkLookerUpper.byNetworkName:
223 NetworkLookerUpper.byNetworkName[network_name] = NetworkLookerUpper(network_name)
224 return NetworkLookerUpper.byNetworkName[network_name]
226 class SliverInline(PlStackTabularInline):
228 fields = ['all_ips_string', 'instance_name', 'slice', 'numberCores', 'deploymentNetwork', 'image', 'node']
230 readonly_fields = ['all_ips_string', 'instance_name']
231 suit_classes = 'suit-tab suit-tab-slivers'
233 def queryset(self, request):
234 return Sliver.select_by_user(request.user)
236 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
237 if db_field.name == 'deploymentNetwork':
238 kwargs['queryset'] = Deployment.select_by_acl(request.user)
239 # the inscrutable jquery selector below says:
240 # find the closest parent "tr" to the current element
241 # then find the child with class "field-node"
242 # then find the child with that is a select
244 kwargs['widget'] = forms.Select(attrs={'onChange': "update_nodes(this, $($(this).closest('tr')[0]).find('.field-node select')[0].id)"})
245 #kwargs['widget'] = forms.Select(attrs={'onChange': "console.log($($($(this).closest('tr')[0]).children('.field-node')[0]).children('select')[0].id);"})
247 field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
252 SMBAKER: This is the old code that implemented each network type as a
253 separate column in the sliver table.
255 def _declared_fieldsets(self):
256 # Return None so django will call get_fieldsets and we can insert our
260 def get_readonly_fields(self, request, obj=None):
261 readonly_fields = list(super(SliverInline, self).get_readonly_fields(request, obj))
263 # Lookup the networks that are bound to the slivers, and add those
264 # network names to the list of readonly fields.
266 for sliver in obj.slivers.all():
267 for nbs in sliver.networksliver_set.all():
269 network_name = nbs.network.name
270 if network_name not in [str(x) for x in readonly_fields]:
271 readonly_fields.append(NetworkLookerUpper.get(network_name))
273 return readonly_fields
275 def get_fieldsets(self, request, obj=None):
276 form = self.get_formset(request, obj).form
277 # fields = the read/write files + the read-only fields
278 fields = list(self.fields)
279 for fieldName in self.get_readonly_fields(request,obj):
280 if not fieldName in fields:
281 fields.append(fieldName)
283 return [(None, {'fields': fields})]
286 class SiteInline(PlStackTabularInline):
289 suit_classes = 'suit-tab suit-tab-sites'
291 def queryset(self, request):
292 return Site.select_by_user(request.user)
294 class UserInline(PlStackTabularInline):
296 fields = ['email', 'firstname', 'lastname']
298 suit_classes = 'suit-tab suit-tab-users'
300 def queryset(self, request):
301 return User.select_by_user(request.user)
303 class SliceInline(PlStackTabularInline):
305 fields = ['name','site', 'serviceClass', 'service']
307 suit_classes = 'suit-tab suit-tab-slices'
309 def queryset(self, request):
310 return Slice.select_by_user(request.user)
312 class NodeInline(PlStackTabularInline):
315 suit_classes = 'suit-tab suit-tab-nodes'
316 fields = ['name','deployment','site']
318 class DeploymentPrivilegeInline(PlStackTabularInline):
319 model = DeploymentPrivilege
321 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
322 fields = ['user','role','deployment']
324 def queryset(self, request):
325 return DeploymentPrivilege.select_by_user(request.user)
327 class SitePrivilegeInline(PlStackTabularInline):
328 model = SitePrivilege
330 suit_classes = 'suit-tab suit-tab-siteprivileges'
331 fields = ['user','site', 'role']
333 def formfield_for_foreignkey(self, db_field, request, **kwargs):
334 if db_field.name == 'site':
335 kwargs['queryset'] = Site.select_by_user(request.user)
337 if db_field.name == 'user':
338 kwargs['queryset'] = User.select_by_user(request.user)
339 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
341 def queryset(self, request):
342 return SitePrivilege.select_by_user(request.user)
344 class SiteDeploymentInline(PlStackTabularInline):
345 model = SiteDeployments
346 #model = Site.deployments.through
348 suit_classes = 'suit-tab suit-tab-deployments'
349 fields = ['deployment','site']
351 def formfield_for_foreignkey(self, db_field, request, **kwargs):
352 if db_field.name == 'site':
353 kwargs['queryset'] = Site.select_by_user(request.user)
355 if db_field.name == 'deployment':
356 kwargs['queryset'] = Deployment.select_by_user(request.user)
357 return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
359 def queryset(self, request):
360 return SiteDeployments.select_by_user(request.user)
363 class SlicePrivilegeInline(PlStackTabularInline):
364 model = SlicePrivilege
365 suit_classes = 'suit-tab suit-tab-sliceprivileges'
367 fields = ('user', 'slice','role')
369 def formfield_for_foreignkey(self, db_field, request, **kwargs):
370 if db_field.name == 'slice':
371 kwargs['queryset'] = Slice.select_by_user(request.user)
372 if db_field.name == 'user':
373 kwargs['queryset'] = User.select_by_user(request.user)
375 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
377 def queryset(self, request):
378 return SlicePrivilege.select_by_user(request.user)
380 class SliceNetworkInline(PlStackTabularInline):
381 model = Network.slices.through
382 selflink_fieldname = "network"
384 verbose_name = "Network Connection"
385 verbose_name_plural = "Network Connections"
386 suit_classes = 'suit-tab suit-tab-slicenetworks'
389 class ImageDeploymentsInline(PlStackTabularInline):
390 model = ImageDeployments
392 verbose_name = "Image Deployments"
393 verbose_name_plural = "Image Deployments"
394 suit_classes = 'suit-tab suit-tab-imagedeployments'
395 fields = ['image', 'deployment', 'glance_image_id']
396 readonly_fields = ['glance_image_id']
398 class PlainTextWidget(forms.HiddenInput):
399 input_type = 'hidden'
401 def render(self, name, value, attrs=None):
404 return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
406 class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
409 def save_model(self, request, obj, form, change):
410 obj.caller = request.user
411 # update openstack connection to use this site/tenant
412 obj.save_by_user(request.user)
414 def delete_model(self, request, obj):
415 obj.delete_by_user(request.user)
417 def save_formset(self, request, form, formset, change):
418 instances = formset.save(commit=False)
419 for instance in instances:
420 instance.save_by_user(request.user)
423 class SliceRoleAdmin(PlanetStackBaseAdmin):
427 class SiteRoleAdmin(PlanetStackBaseAdmin):
431 class DeploymentAdminForm(forms.ModelForm):
432 sites = forms.ModelMultipleChoiceField(
433 queryset=Site.objects.all(),
435 help_text="Select which sites are allowed to host nodes in this deployment",
436 widget=FilteredSelectMultiple(
437 verbose_name=('Sites'), is_stacked=False
440 images = forms.ModelMultipleChoiceField(
441 queryset=Image.objects.all(),
443 help_text="Select which images should be deployed on this deployment",
444 widget=FilteredSelectMultiple(
445 verbose_name=('Images'), is_stacked=False
451 def __init__(self, *args, **kwargs):
452 request = kwargs.pop('request', None)
453 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
455 self.fields['accessControl'].initial = "allow site " + request.user.site.name
457 if self.instance and self.instance.pk:
458 self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments_set.all()]
459 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments_set.all()]
461 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
462 """ helper function for handling m2m relations from the MultipleChoiceField
464 this_obj: the source object we want to link from
466 selected_objs: a list of destination objects we want to link to
468 all_relations: the full set of relations involving this_obj, including ones we don't want
470 relation_class: the class that implements the relation from source to dest
472 local_attrname: field name representing this_obj in relation_class
474 foreign_attrname: field name representing selected_objs in relation_class
476 This function will remove all newobjclass relations from this_obj
477 that are not contained in selected_objs, and add any relations that
478 are in selected_objs but don't exist in the data model yet.
481 existing_dest_objs = []
482 for relation in list(all_relations):
483 if getattr(relation, foreign_attrname) not in selected_objs:
484 #print "deleting site", sdp.site
487 existing_dest_objs.append(getattr(relation, foreign_attrname))
489 for dest_obj in selected_objs:
490 if dest_obj not in existing_dest_objs:
491 #print "adding site", site
492 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
493 relation = relation_class(**kwargs)
496 def save(self, commit=True):
497 deployment = super(DeploymentAdminForm, self).save(commit=False)
503 # save_m2m() doesn't seem to work with 'through' relations. So we
504 # create/destroy the through models ourselves. There has to be
507 self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments_set.all(), SiteDeployments, "deployment", "site")
508 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments_set.all(), ImageDeployments, "deployment", "image")
514 class DeploymentAdminROForm(DeploymentAdminForm):
515 def save(self, commit=True):
516 raise PermissionDenied
518 class SiteAssocInline(PlStackTabularInline):
519 model = Site.deployments.through
521 suit_classes = 'suit-tab suit-tab-sites'
523 class DeploymentAdmin(PlanetStackBaseAdmin):
525 fieldList = ['name','sites', 'images', 'accessControl']
526 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
527 inlines = [DeploymentPrivilegeInline,NodeInline,TagInline] # ,ImageDeploymentsInline]
529 user_readonly_fields = ['name']
531 suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags')) # ,('imagedeployments','Images'))
533 def get_form(self, request, obj=None, **kwargs):
534 if request.user.isReadOnlyUser():
535 kwargs["form"] = DeploymentAdminROForm
537 kwargs["form"] = DeploymentAdminForm
538 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
540 # from stackexchange: pass the request object into the form
542 class AdminFormMetaClass(adminForm):
543 def __new__(cls, *args, **kwargs):
544 kwargs['request'] = request
545 return adminForm(*args, **kwargs)
547 return AdminFormMetaClass
549 class ServiceAttrAsTabInline(PlStackTabularInline):
550 model = ServiceAttribute
551 fields = ['name','value']
553 suit_classes = 'suit-tab suit-tab-serviceattrs'
555 class ServiceAdmin(PlanetStackBaseAdmin):
556 list_display = ("name","description","versionNumber","enabled","published")
557 fieldList = ["name","description","versionNumber","enabled","published"]
558 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
559 inlines = [ServiceAttrAsTabInline,SliceInline]
561 user_readonly_fields = fieldList
563 suit_form_tabs =(('general', 'Service Details'),
565 ('serviceattrs','Additional Attributes'),
568 class SiteAdmin(PlanetStackBaseAdmin):
569 fieldList = ['name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
571 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
572 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
574 suit_form_tabs =(('general', 'Site Details'),
576 ('siteprivileges','Privileges'),
577 ('deployments','Deployments'),
582 readonly_fields = ['accountLink']
584 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
586 list_display = ('name', 'login_base','site_url', 'enabled')
587 filter_horizontal = ('deployments',)
588 inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentInline]
589 search_fields = ['name']
591 def queryset(self, request):
592 return Site.select_by_user(request.user)
594 def get_formsets(self, request, obj=None):
595 for inline in self.get_inline_instances(request, obj):
596 # hide MyInline in the add view
599 if isinstance(inline, SliceInline):
600 inline.model.caller = request.user
601 yield inline.get_formset(request, obj)
603 def get_formsets(self, request, obj=None):
604 for inline in self.get_inline_instances(request, obj):
605 # hide MyInline in the add view
608 if isinstance(inline, SliverInline):
609 inline.model.caller = request.user
610 yield inline.get_formset(request, obj)
612 def accountLink(self, obj):
613 link_obj = obj.accounts.all()
615 reverse_path = "admin:core_account_change"
616 url = reverse(reverse_path, args =(link_obj[0].id,))
617 return "<a href='%s'>%s</a>" % (url, "view billing details")
619 return "no billing data for this site"
620 accountLink.allow_tags = True
621 accountLink.short_description = "Billing"
623 def save_model(self, request, obj, form, change):
624 # update openstack connection to use this site/tenant
625 obj.save_by_user(request.user)
627 def delete_model(self, request, obj):
628 obj.delete_by_user(request.user)
631 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
632 fieldList = ['user', 'site', 'role']
634 (None, {'fields': fieldList, 'classes':['collapse']})
636 list_display = ('user', 'site', 'role')
637 user_readonly_fields = fieldList
638 user_readonly_inlines = []
640 def formfield_for_foreignkey(self, db_field, request, **kwargs):
641 if db_field.name == 'site':
642 if not request.user.is_admin:
643 # only show sites where user is an admin or pi
645 for site_privilege in SitePrivilege.objects.filer(user=request.user):
646 if site_privilege.role.role_type in ['admin', 'pi']:
647 sites.add(site_privilege.site)
648 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
650 if db_field.name == 'user':
651 if not request.user.is_admin:
652 # only show users from sites where caller has admin or pi role
653 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
654 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
655 sites = [site_privilege.site for site_privilege in site_privileges]
656 site_privileges = SitePrivilege.objects.filter(site__in=sites)
657 emails = [site_privilege.user.email for site_privilege in site_privileges]
658 users = User.objects.filter(email__in=emails)
659 kwargs['queryset'] = users
661 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
663 def queryset(self, request):
664 # admins can see all privileges. Users can only see privileges at sites
665 # where they have the admin role or pi role.
666 qs = super(SitePrivilegeAdmin, self).queryset(request)
667 #if not request.user.is_admin:
668 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
669 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
670 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
671 # sites = Site.objects.filter(login_base__in=login_bases)
672 # qs = qs.filter(site__in=sites)
675 class SliceForm(forms.ModelForm):
679 'service': LinkedSelect
682 class SliceAdmin(PlanetStackBaseAdmin):
684 fieldList = ['name', 'site', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
685 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
686 list_display = ('name', 'site','serviceClass', 'slice_url', 'max_slivers')
687 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
689 user_readonly_fields = fieldList
691 suit_form_tabs =(('general', 'Slice Details'),
692 ('slicenetworks','Networks'),
693 ('sliceprivileges','Privileges'),
694 ('slivers','Slivers'),
696 ('reservations','Reservations'),
699 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
700 #deployment_nodes = {}
701 #for node in Node.objects.all():
702 # deployment_nodes[node.deployment.id] = get(deployment_nodes, node.deployment.id, []).append( (node.id, node.name) )
704 deployment_nodes = []
705 for node in Node.objects.all():
706 deployment_nodes.append( (node.deployment.id, node.id, node.name) )
708 context["deployment_nodes"] = deployment_nodes
710 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
712 def formfield_for_foreignkey(self, db_field, request, **kwargs):
713 if db_field.name == 'site':
714 kwargs['queryset'] = Site.select_by_user(request.user)
716 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
718 def queryset(self, request):
719 # admins can see all keys. Users can only see slices they belong to.
720 return Slice.select_by_user(request.user)
722 def get_formsets(self, request, obj=None):
723 for inline in self.get_inline_instances(request, obj):
724 # hide MyInline in the add view
727 if isinstance(inline, SliverInline):
728 inline.model.caller = request.user
729 yield inline.get_formset(request, obj)
732 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
734 (None, {'fields': ['user', 'slice', 'role']})
736 list_display = ('user', 'slice', 'role')
738 user_readonly_fields = ['user', 'slice', 'role']
739 user_readonly_inlines = []
741 def formfield_for_foreignkey(self, db_field, request, **kwargs):
742 if db_field.name == 'slice':
743 kwargs['queryset'] = Slice.select_by_user(request.user)
745 if db_field.name == 'user':
746 kwargs['queryset'] = User.select_by_user(request.user)
748 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
750 def queryset(self, request):
751 # admins can see all memberships. Users can only see memberships of
752 # slices where they have the admin role.
753 return SlicePrivilege.select_by_user(request.user)
755 def save_model(self, request, obj, form, change):
756 # update openstack connection to use this site/tenant
757 auth = request.session.get('auth', {})
758 auth['tenant'] = obj.slice.slicename
759 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
762 def delete_model(self, request, obj):
763 # update openstack connection to use this site/tenant
764 auth = request.session.get('auth', {})
765 auth['tenant'] = obj.slice.slicename
766 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
770 class ImageAdmin(PlanetStackBaseAdmin):
772 fieldsets = [('Image Details',
773 {'fields': ['name', 'disk_format', 'container_format'],
774 'classes': ['suit-tab suit-tab-general']})
777 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
779 inlines = [SliverInline, ImageDeploymentsInline]
781 user_readonly_fields = ['name', 'disk_format', 'container_format']
783 class NodeForm(forms.ModelForm):
786 'site': LinkedSelect,
787 'deployment': LinkedSelect
790 class NodeAdmin(PlanetStackBaseAdmin):
792 list_display = ('name', 'site', 'deployment')
793 list_filter = ('deployment',)
795 inlines = [TagInline,SliverInline]
796 fieldsets = [('Node Details', {'fields': ['name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
798 user_readonly_fields = ['name','site','deployment']
799 user_readonly_inlines = [TagInline,SliverInline]
801 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
804 class SliverForm(forms.ModelForm):
807 ip = forms.CharField(widget=PlainTextWidget)
808 instance_name = forms.CharField(widget=PlainTextWidget)
810 'ip': PlainTextWidget(),
811 'instance_name': PlainTextWidget(),
812 'slice': LinkedSelect,
813 'deploymentNetwork': LinkedSelect,
814 'node': LinkedSelect,
815 'image': LinkedSelect
818 class TagAdmin(PlanetStackBaseAdmin):
819 list_display = ['service', 'name', 'value', 'content_type', 'content_object',]
820 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
821 user_readonly_inlines = []
823 class SliverAdmin(PlanetStackBaseAdmin):
826 ('Sliver Details', {'fields': ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
828 list_display = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']
830 suit_form_tabs =(('general', 'Sliver Details'),
834 inlines = [TagInline]
836 user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image']
838 def formfield_for_foreignkey(self, db_field, request, **kwargs):
839 if db_field.name == 'slice':
840 kwargs['queryset'] = Slice.select_by_user(request.user)
842 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
844 def queryset(self, request):
845 # admins can see all slivers. Users can only see slivers of
846 # the slices they belong to.
847 return Sliver.select_by_user(request.user)
850 def get_formsets(self, request, obj=None):
851 # make some fields read only if we are updating an existing record
853 #self.readonly_fields = ('ip', 'instance_name')
854 self.readonly_fields = ()
856 self.readonly_fields = ()
857 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
859 for inline in self.get_inline_instances(request, obj):
860 # hide MyInline in the add view
863 if isinstance(inline, SliverInline):
864 inline.model.caller = request.user
865 yield inline.get_formset(request, obj)
867 #def save_model(self, request, obj, form, change):
868 # # update openstack connection to use this site/tenant
869 # auth = request.session.get('auth', {})
870 # auth['tenant'] = obj.slice.name
871 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
872 # obj.creator = request.user
875 #def delete_model(self, request, obj):
876 # # update openstack connection to use this site/tenant
877 # auth = request.session.get('auth', {})
878 # auth['tenant'] = obj.slice.name
879 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
882 class UserCreationForm(forms.ModelForm):
883 """A form for creating new users. Includes all the required
884 fields, plus a repeated password."""
885 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
886 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
890 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
892 def clean_password2(self):
893 # Check that the two password entries match
894 password1 = self.cleaned_data.get("password1")
895 password2 = self.cleaned_data.get("password2")
896 if password1 and password2 and password1 != password2:
897 raise forms.ValidationError("Passwords don't match")
900 def save(self, commit=True):
901 # Save the provided password in hashed format
902 user = super(UserCreationForm, self).save(commit=False)
903 user.password = self.cleaned_data["password1"]
904 #user.set_password(self.cleaned_data["password1"])
910 class UserChangeForm(forms.ModelForm):
911 """A form for updating users. Includes all the fields on
912 the user, but replaces the password field with admin's
913 password hash display field.
915 password = ReadOnlyPasswordHashField(label='Password',
916 help_text= '<a href=\"password/\">Change Password</a>.')
921 def clean_password(self):
922 # Regardless of what the user provides, return the initial value.
923 # This is done here, rather than on the field, because the
924 # field does not have access to the initial value
925 return self.initial["password"]
927 class UserDashboardViewInline(PlStackTabularInline):
928 model = UserDashboardView
930 suit_classes = 'suit-tab suit-tab-dashboards'
931 fields = ['user', 'dashboardView', 'order']
933 class UserAdmin(UserAdmin):
937 # The forms to add and change user instances
938 form = UserChangeForm
939 add_form = UserCreationForm
941 # The fields to be used in displaying the User model.
942 # These override the definitions on the base UserAdmin
943 # that reference specific fields on auth.User.
944 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
945 list_filter = ('site',)
946 inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
948 fieldListLoginDetails = ['email','site','password','is_active','is_readonly','is_admin','public_key']
949 fieldListContactInfo = ['firstname','lastname','phone','timezone']
952 ('Login Details', {'fields': ['email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
953 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
954 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
955 #('Important dates', {'fields': ('last_login',)}),
959 'classes': ('wide',),
960 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
963 search_fields = ('email',)
964 ordering = ('email',)
965 filter_horizontal = ()
967 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
969 suit_form_tabs =(('general','Login Details'),
970 ('contact','Contact Information'),
971 ('sliceprivileges','Slice Privileges'),
972 ('siteprivileges','Site Privileges'),
973 ('deploymentprivileges','Deployment Privileges'),
974 ('dashboards','Dashboard Views'))
976 def formfield_for_foreignkey(self, db_field, request, **kwargs):
977 if db_field.name == 'site':
978 kwargs['queryset'] = Site.select_by_user(request.user)
980 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
982 def has_add_permission(self, request, obj=None):
983 return (not self.__user_is_readonly(request))
985 def has_delete_permission(self, request, obj=None):
986 return (not self.__user_is_readonly(request))
988 def get_actions(self,request):
989 actions = super(UserAdmin,self).get_actions(request)
991 if self.__user_is_readonly(request):
992 if 'delete_selected' in actions:
993 del actions['delete_selected']
997 def change_view(self,request,object_id, extra_context=None):
999 if self.__user_is_readonly(request):
1000 if not hasattr(self, "readonly_save"):
1001 # save the original readonly fields
\r
1002 self.readonly_save = self.readonly_fields
\r
1003 self.inlines_save = self.inlines
1004 if hasattr(self, "user_readonly_fields"):
1005 self.readonly_fields=self.user_readonly_fields
1006 if hasattr(self, "user_readonly_inlines"):
1007 self.inlines = self.user_readonly_inlines
1009 if hasattr(self, "readonly_save"):
\r
1010 # restore the original readonly fields
\r
1011 self.readonly_fields = self.readonly_save
\r
1012 self.inlines = self.inlines_save
1015 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1016 except PermissionDenied:
1018 if request.method == 'POST':
1019 raise PermissionDenied
1020 request.readonly = True
1021 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1023 def __user_is_readonly(self, request):
1024 #groups = [x.name for x in request.user.groups.all() ]
1025 #return "readonly" in groups
1026 return request.user.isReadOnlyUser()
1028 def queryset(self, request):
1029 return User.select_by_user(request.user)
1031 class DashboardViewAdmin(PlanetStackBaseAdmin):
1032 fieldsets = [('Dashboard View Details',
1033 {'fields': ['name', 'url'],
1034 'classes': ['suit-tab suit-tab-general']})
1037 suit_form_tabs =(('general','Dashboard View Details'),)
1039 class ServiceResourceInline(PlStackTabularInline):
1040 model = ServiceResource
1043 class ServiceClassAdmin(PlanetStackBaseAdmin):
1044 list_display = ('name', 'commitment', 'membershipFee')
1045 inlines = [ServiceResourceInline]
1047 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1048 user_readonly_inlines = []
1050 class ReservedResourceInline(PlStackTabularInline):
1051 model = ReservedResource
1053 suit_classes = 'suit-tab suit-tab-reservedresources'
1055 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1056 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1058 if db_field.name == 'resource':
1059 # restrict resources to those that the slice's service class allows
1060 if request._slice is not None:
1061 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1062 if len(field.queryset) > 0:
1063 field.initial = field.queryset.all()[0]
1065 field.queryset = field.queryset.none()
\r
1066 elif db_field.name == 'sliver':
\r
1067 # restrict slivers to those that belong to the slice
\r
1068 if request._slice is not None:
\r
1069 field.queryset = field.queryset.filter(slice = request._slice)
1071 field.queryset = field.queryset.none()
\r
1075 def queryset(self, request):
1076 return ReservedResource.select_by_user(request.user)
1078 class ReservationChangeForm(forms.ModelForm):
1082 'slice' : LinkedSelect
1085 class ReservationAddForm(forms.ModelForm):
1086 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1087 refresh = forms.CharField(widget=forms.HiddenInput())
1090 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1092 def clean_slice(self):
1093 slice = self.cleaned_data.get("slice")
1094 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1096 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1102 'slice' : LinkedSelect
1106 class ReservationAddRefreshForm(ReservationAddForm):
1107 """ This form is displayed when the Reservation Form receives an update
1108 from the Slice dropdown onChange handler. It doesn't validate the
1109 data and doesn't save the data. This will cause the form to be
1113 """ don't validate anything other than slice """
1114 dont_validate_fields = ("startTime", "duration")
1116 def full_clean(self):
1117 result = super(ReservationAddForm, self).full_clean()
1119 for fieldname in self.dont_validate_fields:
1120 if fieldname in self._errors:
1121 del self._errors[fieldname]
1125 """ don't save anything """
1129 class ReservationAdmin(PlanetStackBaseAdmin):
1130 fieldList = ['slice', 'startTime', 'duration']
1131 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1132 list_display = ('startTime', 'duration')
1133 form = ReservationAddForm
1135 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1137 inlines = [ReservedResourceInline]
1138 user_readonly_fields = fieldList
1140 def add_view(self, request, form_url='', extra_context=None):
1141 timezone.activate(request.user.timezone)
1142 request._refresh = False
1143 request._slice = None
1144 if request.method == 'POST':
1145 # "refresh" will be set to "1" if the form was submitted due to
1146 # a change in the Slice dropdown.
1147 if request.POST.get("refresh","1") == "1":
1148 request._refresh = True
1149 request.POST["refresh"] = "0"
1151 # Keep track of the slice that was selected, so the
1152 # reservedResource inline can filter items for the slice.
1153 request._slice = request.POST.get("slice",None)
1154 if (request._slice is not None):
1155 request._slice = Slice.objects.get(id=request._slice)
1157 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1160 def changelist_view(self, request, extra_context = None):
1161 timezone.activate(request.user.timezone)
1162 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1164 def get_form(self, request, obj=None, **kwargs):
1167 # For changes, set request._slice to the slice already set in the
1169 request._slice = obj.slice
1170 self.form = ReservationChangeForm
1172 if getattr(request, "_refresh", False):
1173 self.form = ReservationAddRefreshForm
1175 self.form = ReservationAddForm
1176 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1178 def get_readonly_fields(self, request, obj=None):
1179 if (obj is not None):
1180 # Prevent slice from being changed after the reservation has been
1186 def queryset(self, request):
1187 return Reservation.select_by_user(request.user)
1189 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1190 list_display = ("name", )
1191 user_readonly_fields = ['name']
1192 user_readonly_inlines = []
1194 class RouterAdmin(PlanetStackBaseAdmin):
1195 list_display = ("name", )
1196 user_readonly_fields = ['name']
1197 user_readonly_inlines = []
1199 class RouterInline(PlStackTabularInline):
1200 model = Router.networks.through
1202 verbose_name_plural = "Routers"
1203 verbose_name = "Router"
1204 suit_classes = 'suit-tab suit-tab-routers'
1206 class NetworkParameterInline(PlStackGenericTabularInline):
1207 model = NetworkParameter
1209 verbose_name_plural = "Parameters"
1210 verbose_name = "Parameter"
1211 suit_classes = 'suit-tab suit-tab-netparams'
1212 fields = ['parameter', 'value']
1214 class NetworkSliversInline(PlStackTabularInline):
1215 fields = ['network','sliver','ip']
1216 readonly_fields = ("ip", )
1217 model = NetworkSliver
1218 selflink_fieldname = "sliver"
1220 verbose_name_plural = "Slivers"
1221 verbose_name = "Sliver"
1222 suit_classes = 'suit-tab suit-tab-networkslivers'
1224 class NetworkSlicesInline(PlStackTabularInline):
1225 model = NetworkSlice
1226 selflink_fieldname = "slice"
1228 verbose_name_plural = "Slices"
1229 verbose_name = "Slice"
1230 suit_classes = 'suit-tab suit-tab-networkslices'
1231 fields = ['network','slice']
1233 class NetworkAdmin(PlanetStackBaseAdmin):
1234 list_display = ("name", "subnet", "ports", "labels")
1235 readonly_fields = ("subnet", )
1237 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1240 (None, {'fields': ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet'], 'classes':['suit-tab suit-tab-general']}),]
1242 user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1245 ('general','Network Details'),
1246 ('netparams', 'Parameters'),
1247 ('networkslivers','Slivers'),
1248 ('networkslices','Slices'),
1249 ('routers','Routers'),
1251 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1252 list_display = ("name", "guaranteedBandwidth", "visibility")
1253 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1254 user_readonly_inlines = []
1256 # register a signal that caches the user's credentials when they log in
1257 def cache_credentials(sender, user, request, **kwds):
1258 auth = {'username': request.POST['username'],
1259 'password': request.POST['password']}
1260 request.session['auth'] = auth
1261 user_logged_in.connect(cache_credentials)
1263 def dollar_field(fieldName, short_description):
1264 def newFunc(self, obj):
1266 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1268 x=getattr(obj, fieldName, 0.0)
1270 newFunc.short_description = short_description
1273 def right_dollar_field(fieldName, short_description):
1274 def newFunc(self, obj):
1276 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1277 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1279 x=getattr(obj, fieldName, 0.0)
1281 newFunc.short_description = short_description
1282 newFunc.allow_tags = True
1285 class InvoiceChargeInline(PlStackTabularInline):
1288 verbose_name_plural = "Charges"
1289 verbose_name = "Charge"
1290 exclude = ['account']
1291 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1292 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1296 dollar_amount = right_dollar_field("amount", "Amount")
1298 class InvoiceAdmin(admin.ModelAdmin):
1299 list_display = ("date", "account")
1301 inlines = [InvoiceChargeInline]
1303 fields = ["date", "account", "dollar_amount"]
1304 readonly_fields = ["date", "account", "dollar_amount"]
1306 dollar_amount = dollar_field("amount", "Amount")
1308 class InvoiceInline(PlStackTabularInline):
1311 verbose_name_plural = "Invoices"
1312 verbose_name = "Invoice"
1313 fields = ["date", "dollar_amount"]
1314 readonly_fields = ["date", "dollar_amount"]
1315 suit_classes = 'suit-tab suit-tab-accountinvoice'
1319 dollar_amount = right_dollar_field("amount", "Amount")
1321 class PendingChargeInline(PlStackTabularInline):
1324 verbose_name_plural = "Charges"
1325 verbose_name = "Charge"
1326 exclude = ["invoice"]
1327 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1328 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1329 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1333 def queryset(self, request):
1334 qs = super(PendingChargeInline, self).queryset(request)
1335 qs = qs.filter(state="pending")
1338 dollar_amount = right_dollar_field("amount", "Amount")
1340 class PaymentInline(PlStackTabularInline):
1343 verbose_name_plural = "Payments"
1344 verbose_name = "Payment"
1345 fields = ["date", "dollar_amount"]
1346 readonly_fields = ["date", "dollar_amount"]
1347 suit_classes = 'suit-tab suit-tab-accountpayments'
1351 dollar_amount = right_dollar_field("amount", "Amount")
1353 class AccountAdmin(admin.ModelAdmin):
1354 list_display = ("site", "balance_due")
1356 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1359 (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1361 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1364 ('general','Account Details'),
1365 ('accountinvoice', 'Invoices'),
1366 ('accountpayments', 'Payments'),
1367 ('accountpendingcharges','Pending Charges'),
1370 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1371 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1372 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1375 # Now register the new UserAdmin...
1376 admin.site.register(User, UserAdmin)
1377 # ... and, since we're not using Django's builtin permissions,
1378 # unregister the Group model from admin.
1379 #admin.site.unregister(Group)
1381 #Do not show django evolution in the admin interface
1382 from django_evolution.models import Version, Evolution
1383 #admin.site.unregister(Version)
1384 #admin.site.unregister(Evolution)
1387 # When debugging it is often easier to see all the classes, but for regular use
1388 # only the top-levels should be displayed
1391 admin.site.register(Deployment, DeploymentAdmin)
1392 admin.site.register(Site, SiteAdmin)
1393 admin.site.register(Slice, SliceAdmin)
1394 admin.site.register(Service, ServiceAdmin)
1395 admin.site.register(Reservation, ReservationAdmin)
1396 admin.site.register(Network, NetworkAdmin)
1397 admin.site.register(Router, RouterAdmin)
1398 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1399 admin.site.register(Account, AccountAdmin)
1400 admin.site.register(Invoice, InvoiceAdmin)
1403 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1404 admin.site.register(ServiceClass, ServiceClassAdmin)
1405 #admin.site.register(PlanetStack)
1406 admin.site.register(Tag, TagAdmin)
1407 admin.site.register(DeploymentRole)
1408 admin.site.register(SiteRole)
1409 admin.site.register(SliceRole)
1410 admin.site.register(PlanetStackRole)
1411 admin.site.register(Node, NodeAdmin)
1412 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1413 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1414 admin.site.register(Sliver, SliverAdmin)
1415 admin.site.register(Image, ImageAdmin)
1416 admin.site.register(DashboardView, DashboardViewAdmin)