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 self.readonly_fields=self.user_readonly_fields
\r
52 self.inlines = self.user_readonly_inlines
\r
54 if hasattr(self, "readonly_save"):
\r
55 # restore the original readonly fields
\r
56 self.readonly_fields = self.readonly_save
\r
57 self.inlines = self.inlines_save
60 return super(ReadOnlyAwareAdmin, self).change_view(request, object_id, extra_context=extra_context)
61 except PermissionDenied:
63 if request.method == 'POST':
64 raise PermissionDenied
65 request.readonly = True
66 return super(ReadOnlyAwareAdmin, self).change_view(request, object_id, extra_context=extra_context)
69 def __user_is_readonly(self, request):
70 return request.user.isReadOnlyUser()
72 class SingletonAdmin (admin.ModelAdmin):
73 def has_add_permission(self, request):
74 num_objects = self.model.objects.count()
81 class PlStackTabularInline(admin.TabularInline):
82 def __init__(self, *args, **kwargs):
83 super(PlStackTabularInline, self).__init__(*args, **kwargs)
85 # InlineModelAdmin as no get_fields() method, so in order to add
86 # the selflink field, we override __init__ to modify self.fields and
87 # self.readonly_fields.
91 def get_change_url(self, model, id):
92 """ Get the URL to a change form in the admin for this model """
93 reverse_path = "admin:%s_change" % (model._meta.db_table)
95 url = reverse(reverse_path, args=(id,))
96 except NoReverseMatch:
101 def setup_selflink(self):
102 if hasattr(self, "selflink_fieldname"):
103 """ self.selflink_model can be defined to punch through a relation
104 to its target object. For example, in SliceNetworkInline, set
105 selflink_model = "network", and the URL will lead to the Network
106 object instead of trying to bring up a change view of the
109 self.selflink_model = getattr(self.model,self.selflink_fieldname).field.rel.to
111 self.selflink_model = self.model
113 url = self.get_change_url(self.selflink_model, 0)
115 # We don't have an admin for this object, so don't create the
120 # Since we need to add "selflink" to the field list, we need to create
121 # self.fields if it is None.
122 if (self.fields is None):
124 for f in self.model._meta.fields:
125 if f.editable and f.name != "id":
126 self.fields.append(f.name)
128 self.fields = tuple(self.fields) + ("selflink", )
130 if self.readonly_fields is None:
131 self.readonly_fields = ()
133 self.readonly_fields = tuple(self.readonly_fields) + ("selflink", )
135 def selflink(self, obj):
136 if hasattr(self, "selflink_fieldname"):
137 obj = getattr(obj, self.selflink_fieldname)
140 url = self.get_change_url(self.selflink_model, obj.id)
141 return "<a href='%s'>Details</a>" % str(url)
143 return "Not present"
\r
145 selflink.allow_tags = True
146 selflink.short_description = "Details"
148 class ReadOnlyTabularInline(PlStackTabularInline):
151 def get_readonly_fields(self, request, obj=None):
154 def has_add_permission(self, request):
157 class ReservationROInline(ReadOnlyTabularInline):
160 suit_classes = 'suit-tab suit-tab-reservations'
161 fields = ['startTime','slice','duration']
163 class ReservationInline(PlStackTabularInline):
166 suit_classes = 'suit-tab suit-tab-reservations'
168 def queryset(self, request):
169 return Reservation.select_by_user(request.user)
171 class TagROInline(generic.GenericTabularInline):
174 suit_classes = 'suit-tab suit-tab-tags'
176 fields = ['service', 'name', 'value']
178 def get_readonly_fields(self, request, obj=None):
181 def has_add_permission(self, request):
185 class TagInline(generic.GenericTabularInline):
188 suit_classes = 'suit-tab suit-tab-tags'
189 fields = ['service', 'name', 'value']
191 def queryset(self, request):
192 return Tag.select_by_user(request.user)
194 class NetworkLookerUpper:
195 """ This is a callable that looks up a network name in a sliver and returns
196 the ip address for that network.
199 def __init__(self, name):
200 self.short_description = name
202 self.network_name = name
204 def __call__(self, obj):
206 for nbs in obj.networksliver_set.all():
207 if (nbs.network.name == self.network_name):
212 return self.network_name
214 class SliverROInline(ReadOnlyTabularInline):
216 fields = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']
217 suit_classes = 'suit-tab suit-tab-slivers'
219 class SliverInline(PlStackTabularInline):
221 fields = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']
223 readonly_fields = ['ip', 'instance_name']
224 suit_classes = 'suit-tab suit-tab-slivers'
226 def queryset(self, request):
227 return Sliver.select_by_user(request.user)
229 # Note this is breaking in the admin.py when trying to use an inline to add a node/image
230 # def _declared_fieldsets(self):
231 # # Return None so django will call get_fieldsets and we can insert our
235 # def get_readonly_fields(self, request, obj=None):
236 # readonly_fields = super(SliverInline, self).get_readonly_fields(request, obj)
238 # # Lookup the networks that are bound to the slivers, and add those
239 # # network names to the list of readonly fields.
241 # for sliver in obj.slivers.all():
242 # for nbs in sliver.networksliver_set.all():
244 # network_name = nbs.network.name
245 # if network_name not in [str(x) for x in readonly_fields]:
246 # readonly_fields.append(NetworkLookerUpper(network_name))
248 # return readonly_fields
250 # def get_fieldsets(self, request, obj=None):
251 # form = self.get_formset(request, obj).form
252 # # fields = the read/write files + the read-only fields
253 # fields = self.fields
254 # for fieldName in self.get_readonly_fields(request,obj):
255 # if not fieldName in fields:
256 # fields.append(fieldName)
258 # return [(None, {'fields': fields})]
262 class SiteROInline(ReadOnlyTabularInline):
265 fields = ['name', 'login_base', 'site_url', 'enabled']
266 suit_classes = 'suit-tab suit-tab-sites'
268 class SiteInline(PlStackTabularInline):
271 suit_classes = 'suit-tab suit-tab-sites'
273 def queryset(self, request):
274 return Site.select_by_user(request.user)
276 class UserROInline(ReadOnlyTabularInline):
278 fields = ['email', 'firstname', 'lastname']
280 suit_classes = 'suit-tab suit-tab-users'
282 class UserInline(PlStackTabularInline):
284 fields = ['email', 'firstname', 'lastname']
286 suit_classes = 'suit-tab suit-tab-users'
288 def queryset(self, request):
289 return User.select_by_user(request.user)
291 class SliceROInline(ReadOnlyTabularInline):
293 suit_classes = 'suit-tab suit-tab-slices'
294 fields = ['name','site', 'serviceClass', 'service']
296 class SliceInline(PlStackTabularInline):
298 fields = ['name','site', 'serviceClass', 'service']
300 suit_classes = 'suit-tab suit-tab-slices'
302 def queryset(self, request):
303 return Slice.select_by_user(request.user)
305 class NodeROInline(ReadOnlyTabularInline):
308 suit_classes = 'suit-tab suit-tab-nodes'
309 fields = ['name','deployment']
311 class NodeInline(PlStackTabularInline):
314 suit_classes = 'suit-tab suit-tab-nodes'
315 fields = ['name','deployment']
317 class DeploymentPrivilegeROInline(ReadOnlyTabularInline):
318 model = DeploymentPrivilege
320 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
321 fields = ['user','role']
323 class DeploymentPrivilegeInline(PlStackTabularInline):
324 model = DeploymentPrivilege
326 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
327 fields = ['user','role']
329 def queryset(self, request):
330 return DeploymentPrivilege.select_by_user(request.user)
332 #CLEANUP DOUBLE SitePrivilegeInline
333 class SitePrivilegeROInline(ReadOnlyTabularInline):
334 model = SitePrivilege
336 suit_classes = 'suit-tab suit-tab-siteprivileges'
337 fields = ['user','site', 'role']
339 class SitePrivilegeInline(PlStackTabularInline):
340 model = SitePrivilege
342 suit_classes = 'suit-tab suit-tab-siteprivileges'
343 fields = ['user','site', 'role']
345 def formfield_for_foreignkey(self, db_field, request, **kwargs):
346 if db_field.name == 'site':
347 kwargs['queryset'] = Site.select_by_user(request.user)
349 if db_field.name == 'user':
350 kwargs['queryset'] = User.select_by_user(request.user)
351 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
353 def queryset(self, request):
354 return SitePrivilege.select_by_user(request.user)
356 class SiteDeploymentROInline(ReadOnlyTabularInline):
357 model = SiteDeployments
358 #model = Site.deployments.through
360 suit_classes = 'suit-tab suit-tab-sitedeployments'
361 fields = ['deployment','site']
363 class SiteDeploymentInline(PlStackTabularInline):
364 model = SiteDeployments
365 #model = Site.deployments.through
367 suit_classes = 'suit-tab suit-tab-deployments'
368 fields = ['deployment','site']
370 def formfield_for_foreignkey(self, db_field, request, **kwargs):
371 if db_field.name == 'site':
372 kwargs['queryset'] = Site.select_by_user(request.user)
374 if db_field.name == 'deployment':
375 kwargs['queryset'] = Deployment.select_by_user(request.user)
376 return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
378 def queryset(self, request):
379 return SiteDeployments.select_by_user(request.user)
382 class SlicePrivilegeROInline(ReadOnlyTabularInline):
383 model = SlicePrivilege
385 suit_classes = 'suit-tab suit-tab-sliceprivileges'
386 fields = ['user', 'slice', 'role']
388 class SlicePrivilegeInline(PlStackTabularInline):
389 model = SlicePrivilege
390 suit_classes = 'suit-tab suit-tab-sliceprivileges'
392 fields = ('user', 'slice','role')
394 def formfield_for_foreignkey(self, db_field, request, **kwargs):
395 if db_field.name == 'slice':
396 kwargs['queryset'] = Slice.select_by_user(request.user)
397 if db_field.name == 'user':
398 kwargs['queryset'] = User.select_by_user(request.user)
400 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
402 def queryset(self, request):
403 return SlicePrivilege.select_by_user(request.user)
405 class SliceNetworkROInline(ReadOnlyTabularInline):
406 model = Network.slices.through
408 verbose_name = "Network Connection"
409 verbose_name_plural = "Network Connections"
410 suit_classes = 'suit-tab suit-tab-slicenetworks'
413 class SliceNetworkInline(PlStackTabularInline):
414 model = Network.slices.through
415 selflink_fieldname = "network"
417 verbose_name = "Network Connection"
418 verbose_name_plural = "Network Connections"
419 suit_classes = 'suit-tab suit-tab-slicenetworks'
421 class PlainTextWidget(forms.HiddenInput):
422 input_type = 'hidden'
424 def render(self, name, value, attrs=None):
427 return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
429 class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
432 def save_model(self, request, obj, form, change):
433 obj.caller = request.user
434 # update openstack connection to use this site/tenant
435 obj.save_by_user(request.user)
437 def delete_model(self, request, obj):
438 obj.delete_by_user(request.user)
440 def save_formset(self, request, form, formset, change):
441 instances = formset.save(commit=False)
442 for instance in instances:
443 instance.save_by_user(request.user)
446 class SliceRoleAdmin(PlanetStackBaseAdmin):
450 class SiteRoleAdmin(PlanetStackBaseAdmin):
454 class DeploymentAdminForm(forms.ModelForm):
455 sites = forms.ModelMultipleChoiceField(
456 queryset=Site.objects.all(),
458 widget=FilteredSelectMultiple(
459 verbose_name=('Sites'), is_stacked=False
465 def __init__(self, *args, **kwargs):
466 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
468 if self.instance and self.instance.pk:
469 self.fields['sites'].initial = self.instance.sitedeployments_set.all()
471 def save(self, commit=True):
472 deployment = super(DeploymentAdminForm, self).save(commit=False)
478 deployment.sites = self.cleaned_data['sites']
483 class SiteAssocInline(PlStackTabularInline):
484 model = Site.deployments.through
486 suit_classes = 'suit-tab suit-tab-sites'
488 class DeploymentAdmin(PlanetStackBaseAdmin):
489 form = DeploymentAdminForm
491 fieldList = ['name','sites']
492 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
493 inlines = [DeploymentPrivilegeInline,NodeInline,TagInline]
495 user_readonly_inlines = [DeploymentPrivilegeROInline,NodeROInline,TagROInline]
496 user_readonly_fields = ['name']
498 suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags'))
500 class ServiceAttrAsTabROInline(ReadOnlyTabularInline):
501 model = ServiceAttribute
502 fields = ['name','value']
504 suit_classes = 'suit-tab suit-tab-serviceattrs'
506 class ServiceAttrAsTabInline(PlStackTabularInline):
507 model = ServiceAttribute
508 fields = ['name','value']
510 suit_classes = 'suit-tab suit-tab-serviceattrs'
512 class ServiceAdmin(PlanetStackBaseAdmin):
513 list_display = ("name","description","versionNumber","enabled","published")
514 fieldList = ["name","description","versionNumber","enabled","published"]
515 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
516 inlines = [ServiceAttrAsTabInline,SliceInline]
518 user_readonly_fields = fieldList
519 user_readonly_inlines = [ServiceAttrAsTabROInline,SliceROInline]
521 suit_form_tabs =(('general', 'Service Details'),
523 ('serviceattrs','Additional Attributes'),
526 class SiteAdmin(PlanetStackBaseAdmin):
527 fieldList = ['name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
529 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
530 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
532 suit_form_tabs =(('general', 'Site Details'),
534 ('siteprivileges','Privileges'),
535 ('deployments','Deployments'),
540 readonly_fields = ['accountLink']
542 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
543 user_readonly_inlines = [SliceROInline,UserROInline,TagROInline, NodeROInline, SitePrivilegeROInline,SiteDeploymentROInline]
545 list_display = ('name', 'login_base','site_url', 'enabled')
546 filter_horizontal = ('deployments',)
547 inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentInline]
548 search_fields = ['name']
550 def queryset(self, request):
551 #print dir(UserInline)
552 return Site.select_by_user(request.user)
554 def get_formsets(self, request, obj=None):
555 for inline in self.get_inline_instances(request, obj):
556 # hide MyInline in the add view
559 if isinstance(inline, SliceInline):
560 inline.model.caller = request.user
561 yield inline.get_formset(request, obj)
563 def get_formsets(self, request, obj=None):
564 for inline in self.get_inline_instances(request, obj):
565 # hide MyInline in the add view
568 if isinstance(inline, SliverInline):
569 inline.model.caller = request.user
570 yield inline.get_formset(request, obj)
572 def accountLink(self, obj):
573 link_obj = obj.accounts.all()
575 reverse_path = "admin:core_account_change"
576 url = reverse(reverse_path, args =(link_obj[0].id,))
577 return "<a href='%s'>%s</a>" % (url, "view billing details")
579 return "no billing data for this site"
580 accountLink.allow_tags = True
581 accountLink.short_description = "Billing"
583 def save_model(self, request, obj, form, change):
584 # update openstack connection to use this site/tenant
585 obj.save_by_user(request.user)
587 def delete_model(self, request, obj):
588 obj.delete_by_user(request.user)
591 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
592 fieldList = ['user', 'site', 'role']
594 (None, {'fields': fieldList, 'classes':['collapse']})
596 list_display = ('user', 'site', 'role')
597 user_readonly_fields = fieldList
598 user_readonly_inlines = []
600 def formfield_for_foreignkey(self, db_field, request, **kwargs):
601 if db_field.name == 'site':
602 if not request.user.is_admin:
603 # only show sites where user is an admin or pi
605 for site_privilege in SitePrivilege.objects.filer(user=request.user):
606 if site_privilege.role.role_type in ['admin', 'pi']:
607 sites.add(site_privilege.site)
608 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
610 if db_field.name == 'user':
611 if not request.user.is_admin:
612 # only show users from sites where caller has admin or pi role
613 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
614 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
615 sites = [site_privilege.site for site_privilege in site_privileges]
616 site_privileges = SitePrivilege.objects.filter(site__in=sites)
617 emails = [site_privilege.user.email for site_privilege in site_privileges]
618 users = User.objects.filter(email__in=emails)
619 kwargs['queryset'] = users
621 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
623 def queryset(self, request):
624 # admins can see all privileges. Users can only see privileges at sites
625 # where they have the admin role or pi role.
626 qs = super(SitePrivilegeAdmin, self).queryset(request)
627 #if not request.user.is_admin:
628 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
629 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
630 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
631 # sites = Site.objects.filter(login_base__in=login_bases)
632 # qs = qs.filter(site__in=sites)
635 class SliceForm(forms.ModelForm):
639 'service': LinkedSelect
642 class SliceAdmin(PlanetStackBaseAdmin):
644 fieldList = ['name', 'site', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
645 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
646 list_display = ('name', 'site','serviceClass', 'slice_url', 'max_slivers')
647 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
649 user_readonly_fields = fieldList
650 user_readonly_inlines = [SlicePrivilegeROInline,SliverROInline,TagROInline, ReservationROInline, SliceNetworkROInline]
652 suit_form_tabs =(('general', 'Slice Details'),
653 ('slicenetworks','Networks'),
654 ('sliceprivileges','Privileges'),
655 ('slivers','Slivers'),
657 ('reservations','Reservations'),
660 def formfield_for_foreignkey(self, db_field, request, **kwargs):
661 if db_field.name == 'site':
662 kwargs['queryset'] = Site.select_by_user(request.user)
664 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
666 def queryset(self, request):
667 # admins can see all keys. Users can only see slices they belong to.
668 return Slice.select_by_user(request.user)
670 def get_formsets(self, request, obj=None):
671 for inline in self.get_inline_instances(request, obj):
672 # hide MyInline in the add view
675 if isinstance(inline, SliverInline):
676 inline.model.caller = request.user
677 yield inline.get_formset(request, obj)
680 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
682 (None, {'fields': ['user', 'slice', 'role']})
684 list_display = ('user', 'slice', 'role')
686 user_readonly_fields = ['user', 'slice', 'role']
687 user_readonly_inlines = []
689 def formfield_for_foreignkey(self, db_field, request, **kwargs):
690 if db_field.name == 'slice':
691 kwargs['queryset'] = Slice.select_by_user(request.user)
693 if db_field.name == 'user':
694 kwargs['queryset'] = User.select_by_user(request.user)
696 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
698 def queryset(self, request):
699 # admins can see all memberships. Users can only see memberships of
700 # slices where they have the admin role.
701 return SlicePrivilege.select_by_user(request.user)
703 def save_model(self, request, obj, form, change):
704 # update openstack connection to use this site/tenant
705 auth = request.session.get('auth', {})
706 auth['tenant'] = obj.slice.name
707 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
710 def delete_model(self, request, obj):
711 # update openstack connection to use this site/tenant
712 auth = request.session.get('auth', {})
713 auth['tenant'] = obj.slice.name
714 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
718 class ImageAdmin(PlanetStackBaseAdmin):
720 fieldsets = [('Image Details',
721 {'fields': ['name', 'disk_format', 'container_format'],
722 'classes': ['suit-tab suit-tab-general']})
725 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'))
727 inlines = [SliverInline]
729 user_readonly_fields = ['name', 'disk_format', 'container_format']
730 user_readonly_inlines = [SliverROInline]
732 class NodeForm(forms.ModelForm):
735 'site': LinkedSelect,
736 'deployment': LinkedSelect
739 class NodeAdmin(PlanetStackBaseAdmin):
741 list_display = ('name', 'site', 'deployment')
742 list_filter = ('deployment',)
744 inlines = [TagInline,SliverInline]
745 fieldsets = [('Node Details', {'fields': ['name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
747 user_readonly_fields = ['name','site','deployment']
748 user_readonly_inlines = [TagInline,SliverInline]
750 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
753 class SliverForm(forms.ModelForm):
756 ip = forms.CharField(widget=PlainTextWidget)
757 instance_name = forms.CharField(widget=PlainTextWidget)
759 'ip': PlainTextWidget(),
760 'instance_name': PlainTextWidget(),
761 'slice': LinkedSelect,
762 'deploymentNetwork': LinkedSelect,
763 'node': LinkedSelect,
764 'image': LinkedSelect
767 class TagAdmin(PlanetStackBaseAdmin):
768 list_display = ['service', 'name', 'value', 'content_type', 'content_object',]
769 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
770 user_readonly_inlines = []
772 class SliverAdmin(PlanetStackBaseAdmin):
775 ('Sliver Details', {'fields': ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
777 list_display = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']
779 suit_form_tabs =(('general', 'Sliver Details'),
783 inlines = [TagInline]
785 user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image']
786 user_readonly_inlines = [TagROInline]
788 def formfield_for_foreignkey(self, db_field, request, **kwargs):
789 if db_field.name == 'slice':
790 kwargs['queryset'] = Slice.select_by_user(request.user)
792 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
794 def queryset(self, request):
795 # admins can see all slivers. Users can only see slivers of
796 # the slices they belong to.
797 return Sliver.select_by_user(request.user)
800 def get_formsets(self, request, obj=None):
801 # make some fields read only if we are updating an existing record
803 #self.readonly_fields = ('ip', 'instance_name')
804 self.readonly_fields = ()
806 self.readonly_fields = ()
807 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
809 for inline in self.get_inline_instances(request, obj):
810 # hide MyInline in the add view
813 if isinstance(inline, SliverInline):
814 inline.model.caller = request.user
815 yield inline.get_formset(request, obj)
817 #def save_model(self, request, obj, form, change):
818 # # update openstack connection to use this site/tenant
819 # auth = request.session.get('auth', {})
820 # auth['tenant'] = obj.slice.name
821 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
822 # obj.creator = request.user
825 #def delete_model(self, request, obj):
826 # # update openstack connection to use this site/tenant
827 # auth = request.session.get('auth', {})
828 # auth['tenant'] = obj.slice.name
829 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
832 class UserCreationForm(forms.ModelForm):
833 """A form for creating new users. Includes all the required
834 fields, plus a repeated password."""
835 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
836 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
840 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
842 def clean_password2(self):
843 # Check that the two password entries match
844 password1 = self.cleaned_data.get("password1")
845 password2 = self.cleaned_data.get("password2")
846 if password1 and password2 and password1 != password2:
847 raise forms.ValidationError("Passwords don't match")
850 def save(self, commit=True):
851 # Save the provided password in hashed format
852 user = super(UserCreationForm, self).save(commit=False)
853 user.password = self.cleaned_data["password1"]
854 #user.set_password(self.cleaned_data["password1"])
860 class UserChangeForm(forms.ModelForm):
861 """A form for updating users. Includes all the fields on
862 the user, but replaces the password field with admin's
863 password hash display field.
865 password = ReadOnlyPasswordHashField(label='Password',
866 help_text= '<a href=\"password/\">Change Password</a>.')
871 def clean_password(self):
872 # Regardless of what the user provides, return the initial value.
873 # This is done here, rather than on the field, because the
874 # field does not have access to the initial value
875 return self.initial["password"]
877 class UserDashboardViewInline(PlStackTabularInline):
878 model = UserDashboardView
880 suit_classes = 'suit-tab suit-tab-dashboards'
881 fields = ['user', 'dashboardView', 'order']
883 class UserDashboardViewROInline(ReadOnlyTabularInline):
884 model = UserDashboardView
886 suit_classes = 'suit-tab suit-tab-dashboards'
887 fields = ['user', 'dashboardView', 'order']
889 class UserAdmin(UserAdmin):
893 # The forms to add and change user instances
894 form = UserChangeForm
895 add_form = UserCreationForm
897 # The fields to be used in displaying the User model.
898 # These override the definitions on the base UserAdmin
899 # that reference specific fields on auth.User.
900 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
901 #list_display = ('email', 'username','firstname', 'lastname', 'is_admin', 'last_login')
902 list_filter = ('site',)
903 inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
905 fieldListLoginDetails = ['email','site','password','is_readonly','is_amin','public_key']
906 fieldListContactInfo = ['firstname','lastname','phone','timezone']
909 ('Login Details', {'fields': ['email', 'site','password', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
910 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
911 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
912 #('Important dates', {'fields': ('last_login',)}),
916 'classes': ('wide',),
917 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
920 search_fields = ('email',)
921 ordering = ('email',)
922 filter_horizontal = ()
924 user_readonly_fields = fieldListLoginDetails
925 user_readonly_inlines = [SlicePrivilegeROInline,SitePrivilegeROInline,DeploymentPrivilegeROInline,UserDashboardViewROInline]
927 suit_form_tabs =(('general','Login Details'),
928 ('contact','Contact Information'),
929 ('sliceprivileges','Slice Privileges'),
930 ('siteprivileges','Site Privileges'),
931 ('deploymentprivileges','Deployment Privileges'),
932 ('dashboards','Dashboard Views'))
934 def formfield_for_foreignkey(self, db_field, request, **kwargs):
935 if db_field.name == 'site':
936 kwargs['queryset'] = Site.select_by_user(request.user)
938 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
940 def has_add_permission(self, request, obj=None):
941 return (not self.__user_is_readonly(request))
943 def has_delete_permission(self, request, obj=None):
944 return (not self.__user_is_readonly(request))
946 def get_actions(self,request):
947 actions = super(UserAdmin,self).get_actions(request)
949 if self.__user_is_readonly(request):
950 if 'delete_selected' in actions:
951 del actions['delete_selected']
955 def change_view(self,request,object_id, extra_context=None):
957 if self.__user_is_readonly(request):
958 self.readonly_fields=self.user_readonly_fields
959 self.inlines = self.user_readonly_inlines
961 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
962 except PermissionDenied:
964 if request.method == 'POST':
965 raise PermissionDenied
966 request.readonly = True
967 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
969 def __user_is_readonly(self, request):
970 #groups = [x.name for x in request.user.groups.all() ]
971 #return "readonly" in groups
972 return request.user.isReadOnlyUser()
974 def queryset(self, request):
975 return User.select_by_user(request.user)
977 class DashboardViewAdmin(PlanetStackBaseAdmin):
978 fieldsets = [('Dashboard View Details',
979 {'fields': ['name', 'url'],
980 'classes': ['suit-tab suit-tab-general']})
983 suit_form_tabs =(('general','Dashboard View Details'),)
985 class ServiceResourceROInline(ReadOnlyTabularInline):
986 model = ServiceResource
988 fields = ['serviceClass', 'name', 'maxUnitsDeployment', 'maxUnitsNode', 'maxDuration', 'bucketInRate', 'bucketMaxSize', 'cost', 'calendarReservable']
990 class ServiceResourceInline(PlStackTabularInline):
991 model = ServiceResource
994 class ServiceClassAdmin(PlanetStackBaseAdmin):
995 list_display = ('name', 'commitment', 'membershipFee')
996 inlines = [ServiceResourceInline]
998 user_readonly_fields = ['name', 'commitment', 'membershipFee']
999 user_readonly_inlines = []
1001 class ReservedResourceROInline(ReadOnlyTabularInline):
1002 model = ReservedResource
1004 fields = ['sliver', 'resource','quantity','reservationSet']
1005 suit_classes = 'suit-tab suit-tab-reservedresources'
1007 class ReservedResourceInline(PlStackTabularInline):
1008 model = ReservedResource
1010 suit_classes = 'suit-tab suit-tab-reservedresources'
1012 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1013 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1015 if db_field.name == 'resource':
1016 # restrict resources to those that the slice's service class allows
1017 if request._slice is not None:
1018 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1019 if len(field.queryset) > 0:
1020 field.initial = field.queryset.all()[0]
1022 field.queryset = field.queryset.none()
\r
1023 elif db_field.name == 'sliver':
\r
1024 # restrict slivers to those that belong to the slice
\r
1025 if request._slice is not None:
\r
1026 field.queryset = field.queryset.filter(slice = request._slice)
1028 field.queryset = field.queryset.none()
\r
1032 def queryset(self, request):
1033 return ReservedResource.select_by_user(request.user)
1035 class ReservationChangeForm(forms.ModelForm):
1039 'slice' : LinkedSelect
1042 class ReservationAddForm(forms.ModelForm):
1043 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1044 refresh = forms.CharField(widget=forms.HiddenInput())
1047 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1049 def clean_slice(self):
1050 slice = self.cleaned_data.get("slice")
1051 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1053 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1059 'slice' : LinkedSelect
1063 class ReservationAddRefreshForm(ReservationAddForm):
1064 """ This form is displayed when the Reservation Form receives an update
1065 from the Slice dropdown onChange handler. It doesn't validate the
1066 data and doesn't save the data. This will cause the form to be
1070 """ don't validate anything other than slice """
1071 dont_validate_fields = ("startTime", "duration")
1073 def full_clean(self):
1074 result = super(ReservationAddForm, self).full_clean()
1076 for fieldname in self.dont_validate_fields:
1077 if fieldname in self._errors:
1078 del self._errors[fieldname]
1082 """ don't save anything """
1086 class ReservationAdmin(PlanetStackBaseAdmin):
1087 fieldList = ['slice', 'startTime', 'duration']
1088 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1089 list_display = ('startTime', 'duration')
1090 form = ReservationAddForm
1092 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1094 inlines = [ReservedResourceInline]
1095 user_readonly_inlines = [ReservedResourceROInline]
1096 user_readonly_fields = fieldList
1098 def add_view(self, request, form_url='', extra_context=None):
1099 timezone.activate(request.user.timezone)
1100 request._refresh = False
1101 request._slice = None
1102 if request.method == 'POST':
1103 # "refresh" will be set to "1" if the form was submitted due to
1104 # a change in the Slice dropdown.
1105 if request.POST.get("refresh","1") == "1":
1106 request._refresh = True
1107 request.POST["refresh"] = "0"
1109 # Keep track of the slice that was selected, so the
1110 # reservedResource inline can filter items for the slice.
1111 request._slice = request.POST.get("slice",None)
1112 if (request._slice is not None):
1113 request._slice = Slice.objects.get(id=request._slice)
1115 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1118 def changelist_view(self, request, extra_context = None):
1119 timezone.activate(request.user.timezone)
1120 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1122 def get_form(self, request, obj=None, **kwargs):
1125 # For changes, set request._slice to the slice already set in the
1127 request._slice = obj.slice
1128 self.form = ReservationChangeForm
1130 if getattr(request, "_refresh", False):
1131 self.form = ReservationAddRefreshForm
1133 self.form = ReservationAddForm
1134 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1136 def get_readonly_fields(self, request, obj=None):
1137 if (obj is not None):
1138 # Prevent slice from being changed after the reservation has been
1144 def queryset(self, request):
1145 return Reservation.select_by_user(request.user)
1147 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1148 list_display = ("name", )
1149 user_readonly_fields = ['name']
1150 user_readonly_inlines = []
1152 class RouterAdmin(PlanetStackBaseAdmin):
1153 list_display = ("name", )
1154 user_readonly_fields = ['name']
1155 user_readonly_inlines = []
1157 class RouterROInline(ReadOnlyTabularInline):
1158 model = Router.networks.through
1160 verbose_name_plural = "Routers"
1161 verbose_name = "Router"
1162 suit_classes = 'suit-tab suit-tab-routers'
1164 fields = ['name', 'owner', 'permittedNetworks', 'networks']
1166 class RouterInline(PlStackTabularInline):
1167 model = Router.networks.through
1169 verbose_name_plural = "Routers"
1170 verbose_name = "Router"
1171 suit_classes = 'suit-tab suit-tab-routers'
1173 class NetworkParameterROInline(ReadOnlyTabularInline):
1174 model = NetworkParameter
1176 verbose_name_plural = "Parameters"
1177 verbose_name = "Parameter"
1178 suit_classes = 'suit-tab suit-tab-netparams'
1179 fields = ['parameter', 'value', 'content_type', 'object_id', 'content_object']
1181 class NetworkParameterInline(generic.GenericTabularInline):
1182 model = NetworkParameter
1184 verbose_name_plural = "Parameters"
1185 verbose_name = "Parameter"
1186 suit_classes = 'suit-tab suit-tab-netparams'
1188 class NetworkSliversROInline(ReadOnlyTabularInline):
1189 fields = ['network', 'sliver', 'ip', 'port_id']
1190 model = NetworkSliver
1192 verbose_name_plural = "Slivers"
1193 verbose_name = "Sliver"
1194 suit_classes = 'suit-tab suit-tab-networkslivers'
1196 class NetworkSliversInline(PlStackTabularInline):
1197 readonly_fields = ("ip", )
1198 model = NetworkSliver
1199 selflink_fieldname = "sliver"
1201 verbose_name_plural = "Slivers"
1202 verbose_name = "Sliver"
1203 suit_classes = 'suit-tab suit-tab-networkslivers'
1205 class NetworkSlicesROInline(ReadOnlyTabularInline):
1206 model = NetworkSlice
1208 verbose_name_plural = "Slices"
1209 verbose_name = "Slice"
1210 suit_classes = 'suit-tab suit-tab-networkslices'
1211 fields = ['network','slice']
1213 class NetworkSlicesInline(PlStackTabularInline):
1214 model = NetworkSlice
1215 selflink_fieldname = "slice"
1217 verbose_name_plural = "Slices"
1218 verbose_name = "Slice"
1219 suit_classes = 'suit-tab suit-tab-networkslices'
1221 class NetworkAdmin(PlanetStackBaseAdmin):
1222 list_display = ("name", "subnet", "ports", "labels")
1223 readonly_fields = ("subnet", )
1225 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1228 (None, {'fields': ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet'], 'classes':['suit-tab suit-tab-general']}),]
1230 user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1231 user_readonly_inlines = [NetworkParameterROInline, NetworkSliversROInline, NetworkSlicesROInline, RouterROInline]
1234 ('general','Network Details'),
1235 ('netparams', 'Parameters'),
1236 ('networkslivers','Slivers'),
1237 ('networkslices','Slices'),
1238 ('routers','Routers'),
1240 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1241 list_display = ("name", "guaranteedBandwidth", "visibility")
1242 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1243 user_readonly_inlines = []
1245 # register a signal that caches the user's credentials when they log in
1246 def cache_credentials(sender, user, request, **kwds):
1247 auth = {'username': request.POST['username'],
1248 'password': request.POST['password']}
1249 request.session['auth'] = auth
1250 user_logged_in.connect(cache_credentials)
1252 def dollar_field(fieldName, short_description):
1253 def newFunc(self, obj):
1255 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1257 x=getattr(obj, fieldName, 0.0)
1259 newFunc.short_description = short_description
1262 def right_dollar_field(fieldName, short_description):
1263 def newFunc(self, obj):
1265 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1266 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1268 x=getattr(obj, fieldName, 0.0)
1270 newFunc.short_description = short_description
1271 newFunc.allow_tags = True
1274 class InvoiceChargeInline(PlStackTabularInline):
1277 verbose_name_plural = "Charges"
1278 verbose_name = "Charge"
1279 exclude = ['account']
1280 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1281 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1285 dollar_amount = right_dollar_field("amount", "Amount")
1287 class InvoiceAdmin(admin.ModelAdmin):
1288 list_display = ("date", "account")
1290 inlines = [InvoiceChargeInline]
1292 fields = ["date", "account", "dollar_amount"]
1293 readonly_fields = ["date", "account", "dollar_amount"]
1295 dollar_amount = dollar_field("amount", "Amount")
1297 class InvoiceInline(PlStackTabularInline):
1300 verbose_name_plural = "Invoices"
1301 verbose_name = "Invoice"
1302 fields = ["date", "dollar_amount"]
1303 readonly_fields = ["date", "dollar_amount"]
1304 suit_classes = 'suit-tab suit-tab-accountinvoice'
1308 dollar_amount = right_dollar_field("amount", "Amount")
1310 class PendingChargeInline(PlStackTabularInline):
1313 verbose_name_plural = "Charges"
1314 verbose_name = "Charge"
1315 exclude = ["invoice"]
1316 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1317 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1318 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1322 def queryset(self, request):
1323 qs = super(PendingChargeInline, self).queryset(request)
1324 qs = qs.filter(state="pending")
1327 dollar_amount = right_dollar_field("amount", "Amount")
1329 class PaymentInline(PlStackTabularInline):
1332 verbose_name_plural = "Payments"
1333 verbose_name = "Payment"
1334 fields = ["date", "dollar_amount"]
1335 readonly_fields = ["date", "dollar_amount"]
1336 suit_classes = 'suit-tab suit-tab-accountpayments'
1340 dollar_amount = right_dollar_field("amount", "Amount")
1342 class AccountAdmin(admin.ModelAdmin):
1343 list_display = ("site", "balance_due")
1345 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1348 (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1350 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1353 ('general','Account Details'),
1354 ('accountinvoice', 'Invoices'),
1355 ('accountpayments', 'Payments'),
1356 ('accountpendingcharges','Pending Charges'),
1359 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1360 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1361 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1364 # Now register the new UserAdmin...
1365 admin.site.register(User, UserAdmin)
1366 # ... and, since we're not using Django's builtin permissions,
1367 # unregister the Group model from admin.
1368 #admin.site.unregister(Group)
1370 #Do not show django evolution in the admin interface
1371 from django_evolution.models import Version, Evolution
1372 #admin.site.unregister(Version)
1373 #admin.site.unregister(Evolution)
1376 # When debugging it is often easier to see all the classes, but for regular use
1377 # only the top-levels should be displayed
1380 admin.site.register(Deployment, DeploymentAdmin)
1381 admin.site.register(Site, SiteAdmin)
1382 admin.site.register(Slice, SliceAdmin)
1383 admin.site.register(Service, ServiceAdmin)
1384 admin.site.register(Reservation, ReservationAdmin)
1385 admin.site.register(Network, NetworkAdmin)
1386 admin.site.register(Router, RouterAdmin)
1387 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1388 admin.site.register(Account, AccountAdmin)
1389 admin.site.register(Invoice, InvoiceAdmin)
1392 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1393 admin.site.register(ServiceClass, ServiceClassAdmin)
1394 #admin.site.register(PlanetStack)
1395 admin.site.register(Tag, TagAdmin)
1396 admin.site.register(DeploymentRole)
1397 admin.site.register(SiteRole)
1398 admin.site.register(SliceRole)
1399 admin.site.register(PlanetStackRole)
1400 admin.site.register(Node, NodeAdmin)
1401 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1402 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1403 admin.site.register(Sliver, SliverAdmin)
1404 admin.site.register(Image, ImageAdmin)
1405 admin.site.register(DashboardView, DashboardViewAdmin)