Merge branch 'master' of ssh://git.planet-lab.org/git/plstackapi
[plstackapi.git] / planetstack / core / admin.py
1 from core.models import Site
2 from core.models import *
3 from openstack.manager import OpenStackManager
4
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
18
19 import django_evolution 
20
21 class ReadOnlyAwareAdmin(admin.ModelAdmin):
22
23     def has_add_permission(self, request, obj=None):
24         return (not self.__user_is_readonly(request))
25  
26     def has_delete_permission(self, request, obj=None):
27         return (not self.__user_is_readonly(request))
28
29     def save_model(self, request, obj, form, change):
30         if self.__user_is_readonly(request):
31             raise PermissionDenied
32             #pass
33         else:
34             return super(ReadOnlyAwareAdmin, self).save_model(request, obj, form, change)
35
36     def get_actions(self,request):
37         actions = super(ReadOnlyAwareAdmin,self).get_actions(request)
38
39         if self.__user_is_readonly(request):
40             if 'delete_selected' in actions:
41                 del actions['delete_selected']
42
43         return actions
44
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
55         else:\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
61
62         try:
63             return super(ReadOnlyAwareAdmin, self).change_view(request, object_id, extra_context=extra_context)
64         except PermissionDenied:
65             pass
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)
70
71     def __user_is_readonly(self, request):
72         return request.user.isReadOnlyUser()
73
74 class SingletonAdmin (ReadOnlyAwareAdmin):
75     def has_add_permission(self, request):
76         if not super(SingletonAdmin, self).has_add_permission(request):
77             return False
78
79         num_objects = self.model.objects.count()
80         if num_objects >= 1:
81             return False
82         else:
83             return True
84
85
86 class PlStackTabularInline(admin.TabularInline):
87     def __init__(self, *args, **kwargs):
88         super(PlStackTabularInline, self).__init__(*args, **kwargs)
89
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.
93
94         self.setup_selflink()
95
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)
99         try:
100             url = reverse(reverse_path, args=(id,))
101         except NoReverseMatch:
102             return None
103
104         return url
105
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
112                 SliceNetwork object.
113             """
114             self.selflink_model = getattr(self.model,self.selflink_fieldname).field.rel.to
115         else:
116             self.selflink_model = self.model
117
118         url = self.get_change_url(self.selflink_model, 0)
119
120         # We don't have an admin for this object, so don't create the
121         # selflink.
122         if (url == None):
123             return
124
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):
128             self.fields = []
129             for f in self.model._meta.fields:
130                 if f.editable and f.name != "id":
131                     self.fields.append(f.name)
132
133         self.fields = tuple(self.fields) + ("selflink", )
134
135         if self.readonly_fields is None:
136             self.readonly_fields = ()
137
138         self.readonly_fields = tuple(self.readonly_fields) + ("selflink", )
139
140     def selflink(self, obj):
141         if hasattr(self, "selflink_fieldname"):
142             obj = getattr(obj, self.selflink_fieldname)
143
144         if obj.id:
145             url = self.get_change_url(self.selflink_model, obj.id)
146             return "<a href='%s'>Details</a>" % str(url)
147         else:\r
148             return "Not present"\r
149
150     selflink.allow_tags = True
151     selflink.short_description = "Details"
152
153 class ReadOnlyTabularInline(PlStackTabularInline):
154     can_delete = False
155
156     def get_readonly_fields(self, request, obj=None):
157         return self.fields
158
159     def has_add_permission(self, request):
160         return False
161
162 class ReservationROInline(ReadOnlyTabularInline):
163     model = Reservation
164     extra = 0
165     suit_classes = 'suit-tab suit-tab-reservations'
166     fields = ['startTime','slice','duration']
167
168 class ReservationInline(PlStackTabularInline):
169     model = Reservation
170     extra = 0
171     suit_classes = 'suit-tab suit-tab-reservations'
172         
173     def queryset(self, request):
174         return Reservation.select_by_user(request.user)
175
176 class TagROInline(generic.GenericTabularInline):
177     model = Tag
178     extra = 0
179     suit_classes = 'suit-tab suit-tab-tags'
180     can_delete = False
181     fields = ['service', 'name', 'value']
182
183     def get_readonly_fields(self, request, obj=None):
184         return self.fields
185
186     def has_add_permission(self, request):
187         return False
188
189
190 class TagInline(generic.GenericTabularInline):
191     model = Tag
192     extra = 0
193     suit_classes = 'suit-tab suit-tab-tags'
194     fields = ['service', 'name', 'value']
195
196     def queryset(self, request):
197         return Tag.select_by_user(request.user)
198
199 class NetworkLookerUpper:
200     """ This is a callable that looks up a network name in a sliver and returns
201         the ip address for that network.
202     """
203
204     def __init__(self, name):
205         self.short_description = name
206         self.__name__ = name
207         self.network_name = name
208
209     def __call__(self, obj):
210         if obj is not None:
211             for nbs in obj.networksliver_set.all():
212                 if (nbs.network.name == self.network_name):
213                     return nbs.ip
214         return ""
215
216     def __str__(self):
217         return self.network_name
218
219 class SliverROInline(ReadOnlyTabularInline):
220     model = Sliver
221     fields = ['ip', 'instance_name', 'slice', 'numberCores', 'deploymentNetwork', 'image', 'node']
222     suit_classes = 'suit-tab suit-tab-slivers'
223
224 class SliverInline(PlStackTabularInline):
225     model = Sliver
226     fields = ['ip', 'instance_name', 'slice', 'numberCores', 'deploymentNetwork', 'image', 'node']
227     extra = 0
228     readonly_fields = ['ip', 'instance_name']
229     suit_classes = 'suit-tab suit-tab-slivers'
230
231     def queryset(self, request):
232         return Sliver.select_by_user(request.user)
233
234     def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
235         if db_field.name == 'deploymentNetwork':
236            kwargs['queryset'] = Deployment.select_by_acl(request.user)
237            # the inscrutable jquery selector below says:
238            #     find the closest parent "tr" to the current element
239            #     then find the child with class "field-node"
240            #     then find the child with that is a select
241            #     then return its id
242            kwargs['widget'] = forms.Select(attrs={'onChange': "update_nodes(this, $($(this).closest('tr')[0]).find('.field-node select')[0].id)"})
243            #kwargs['widget'] = forms.Select(attrs={'onChange': "console.log($($($(this).closest('tr')[0]).children('.field-node')[0]).children('select')[0].id);"})
244
245         field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
246
247         return field
248
249 # Note this is breaking in the admin.py when trying to use an inline to add a node/image 
250 #    def _declared_fieldsets(self):
251 #        # Return None so django will call get_fieldsets and we can insert our
252 #        # dynamic fields
253 #        return None
254 #
255 #    def get_readonly_fields(self, request, obj=None):
256 #        readonly_fields = super(SliverInline, self).get_readonly_fields(request, obj)
257 #
258 #        # Lookup the networks that are bound to the slivers, and add those
259 #        # network names to the list of readonly fields.
260 #
261 #        for sliver in obj.slivers.all():
262 #            for nbs in sliver.networksliver_set.all():
263 #                if nbs.ip:
264 #                    network_name = nbs.network.name
265 #                    if network_name not in [str(x) for x in readonly_fields]:
266 #                        readonly_fields.append(NetworkLookerUpper(network_name))
267 #
268 #        return readonly_fields
269 #
270 #    def get_fieldsets(self, request, obj=None):
271 #        form = self.get_formset(request, obj).form
272 #        # fields = the read/write files + the read-only fields
273 #        fields = self.fields
274 #        for fieldName in self.get_readonly_fields(request,obj):
275 #            if not fieldName in fields:
276 #                fields.append(fieldName)
277 #
278 #        return [(None, {'fields': fields})]
279
280     
281
282 class SiteROInline(ReadOnlyTabularInline):
283     model = Site
284     extra = 0
285     fields = ['name', 'login_base', 'site_url', 'enabled'] 
286     suit_classes = 'suit-tab suit-tab-sites'
287
288 class SiteInline(PlStackTabularInline):
289     model = Site
290     extra = 0
291     suit_classes = 'suit-tab suit-tab-sites'
292
293     def queryset(self, request):
294         return Site.select_by_user(request.user)
295
296 class UserROInline(ReadOnlyTabularInline):
297     model = User
298     fields = ['email', 'firstname', 'lastname']
299     extra = 0
300     suit_classes = 'suit-tab suit-tab-users'
301
302 class UserInline(PlStackTabularInline):
303     model = User
304     fields = ['email', 'firstname', 'lastname']
305     extra = 0
306     suit_classes = 'suit-tab suit-tab-users'
307
308     def queryset(self, request):
309         return User.select_by_user(request.user)
310
311 class SliceROInline(ReadOnlyTabularInline):
312     model = Slice
313     suit_classes = 'suit-tab suit-tab-slices'
314     fields = ['name','site', 'serviceClass', 'service']
315
316 class SliceInline(PlStackTabularInline):
317     model = Slice
318     fields = ['name','site', 'serviceClass', 'service']
319     extra = 0
320     suit_classes = 'suit-tab suit-tab-slices'
321
322     def queryset(self, request):
323         return Slice.select_by_user(request.user)
324
325 class NodeROInline(ReadOnlyTabularInline):
326     model = Node
327     extra = 0
328     suit_classes = 'suit-tab suit-tab-nodes'
329     fields = ['name','deployment','site']
330
331 class NodeInline(PlStackTabularInline):
332     model = Node
333     extra = 0
334     suit_classes = 'suit-tab suit-tab-nodes'
335     fields = ['name','deployment','site']
336
337 class DeploymentPrivilegeROInline(ReadOnlyTabularInline):
338     model = DeploymentPrivilege
339     extra = 0
340     suit_classes = 'suit-tab suit-tab-deploymentprivileges'
341     fields = ['user','role','deployment']
342
343 class DeploymentPrivilegeInline(PlStackTabularInline):
344     model = DeploymentPrivilege
345     extra = 0
346     suit_classes = 'suit-tab suit-tab-deploymentprivileges'
347     fields = ['user','role','deployment']
348
349     def queryset(self, request):
350         return DeploymentPrivilege.select_by_user(request.user)
351
352 #CLEANUP DOUBLE SitePrivilegeInline
353 class SitePrivilegeROInline(ReadOnlyTabularInline):
354     model = SitePrivilege
355     extra = 0
356     suit_classes = 'suit-tab suit-tab-siteprivileges'
357     fields = ['user','site', 'role']
358
359 class SitePrivilegeInline(PlStackTabularInline):
360     model = SitePrivilege
361     extra = 0
362     suit_classes = 'suit-tab suit-tab-siteprivileges'
363     fields = ['user','site', 'role']
364
365     def formfield_for_foreignkey(self, db_field, request, **kwargs):
366         if db_field.name == 'site':
367             kwargs['queryset'] = Site.select_by_user(request.user)
368
369         if db_field.name == 'user':
370             kwargs['queryset'] = User.select_by_user(request.user)
371         return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
372
373     def queryset(self, request):
374         return SitePrivilege.select_by_user(request.user)
375
376 class SiteDeploymentROInline(ReadOnlyTabularInline):
377     model = SiteDeployments
378     #model = Site.deployments.through
379     extra = 0
380     suit_classes = 'suit-tab suit-tab-deployments'
381     fields = ['deployment','site']
382
383 class SiteDeploymentInline(PlStackTabularInline):
384     model = SiteDeployments
385     #model = Site.deployments.through
386     extra = 0
387     suit_classes = 'suit-tab suit-tab-deployments'
388     fields = ['deployment','site']
389
390     def formfield_for_foreignkey(self, db_field, request, **kwargs):
391         if db_field.name == 'site':
392             kwargs['queryset'] = Site.select_by_user(request.user)
393
394         if db_field.name == 'deployment':
395             kwargs['queryset'] = Deployment.select_by_user(request.user)
396         return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
397
398     def queryset(self, request):
399         return SiteDeployments.select_by_user(request.user)
400
401
402 class SlicePrivilegeROInline(ReadOnlyTabularInline):
403     model = SlicePrivilege
404     extra = 0
405     suit_classes = 'suit-tab suit-tab-sliceprivileges'
406     fields = ['user', 'slice', 'role']
407
408 class SlicePrivilegeInline(PlStackTabularInline):
409     model = SlicePrivilege
410     suit_classes = 'suit-tab suit-tab-sliceprivileges'
411     extra = 0
412     fields = ('user', 'slice','role')
413
414     def formfield_for_foreignkey(self, db_field, request, **kwargs):
415         if db_field.name == 'slice':
416            kwargs['queryset'] = Slice.select_by_user(request.user) 
417         if db_field.name == 'user':
418            kwargs['queryset'] = User.select_by_user(request.user) 
419
420         return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
421
422     def queryset(self, request):
423         return SlicePrivilege.select_by_user(request.user)
424
425 class SliceNetworkROInline(ReadOnlyTabularInline):
426     model = Network.slices.through
427     extra = 0
428     verbose_name = "Network Connection"
429     verbose_name_plural = "Network Connections"
430     suit_classes = 'suit-tab suit-tab-slicenetworks'
431     fields = ['network']
432
433 class SliceNetworkInline(PlStackTabularInline):
434     model = Network.slices.through
435     selflink_fieldname = "network"
436     extra = 0
437     verbose_name = "Network Connection"
438     verbose_name_plural = "Network Connections"
439     suit_classes = 'suit-tab suit-tab-slicenetworks'
440     fields = ['network']
441
442 class ImageDeploymentsROInline(ReadOnlyTabularInline):
443     model = ImageDeployments
444     extra = 0
445     verbose_name = "Image Deployments"
446     verbose_name_plural = "Image Deployments"
447     suit_classes = 'suit-tab suit-tab-imagedeployments'
448     fields = ['image', 'deployment', 'glance_image_id']
449
450 class ImageDeploymentsInline(PlStackTabularInline):
451     model = ImageDeployments
452     extra = 0
453     verbose_name = "Image Deployments"
454     verbose_name_plural = "Image Deployments"
455     suit_classes = 'suit-tab suit-tab-imagedeployments'
456     fields = ['image', 'deployment', 'glance_image_id']
457     readonly_fields = ['glance_image_id']
458
459 class PlainTextWidget(forms.HiddenInput):
460     input_type = 'hidden'
461
462     def render(self, name, value, attrs=None):
463         if value is None:
464             value = ''
465         return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
466
467 class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
468     save_on_top = False
469     
470     def save_model(self, request, obj, form, change):
471         obj.caller = request.user
472         # update openstack connection to use this site/tenant
473         obj.save_by_user(request.user)
474
475     def delete_model(self, request, obj):
476         obj.delete_by_user(request.user)
477
478     def save_formset(self, request, form, formset, change):
479         instances = formset.save(commit=False)
480         for instance in instances:
481             instance.save_by_user(request.user)
482         formset.save_m2m()
483
484 class SliceRoleAdmin(PlanetStackBaseAdmin):
485     model = SliceRole
486     pass
487
488 class SiteRoleAdmin(PlanetStackBaseAdmin):
489     model = SiteRole
490     pass
491
492 class DeploymentAdminForm(forms.ModelForm):
493     sites = forms.ModelMultipleChoiceField(
494         queryset=Site.objects.all(),
495         required=False,
496         help_text="Select which sites are allowed to host nodes in this deployment",
497         widget=FilteredSelectMultiple(
498             verbose_name=('Sites'), is_stacked=False
499         )
500     )
501     images = forms.ModelMultipleChoiceField(
502         queryset=Image.objects.all(),
503         required=False,
504         help_text="Select which images should be deployed on this deployment",
505         widget=FilteredSelectMultiple(
506             verbose_name=('Images'), is_stacked=False
507         )
508     )
509     class Meta:
510         model = Deployment
511
512     def __init__(self, *args, **kwargs):
513       request = kwargs.pop('request', None)
514       super(DeploymentAdminForm, self).__init__(*args, **kwargs)
515
516       self.fields['accessControl'].initial = "allow site " + request.user.site.name
517
518       if self.instance and self.instance.pk:
519         self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments_set.all()]
520         self.fields['images'].initial = [x.image for x in self.instance.imagedeployments_set.all()]
521
522     def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
523         """ helper function for handling m2m relations from the MultipleChoiceField
524
525             this_obj: the source object we want to link from
526
527             selected_objs: a list of destination objects we want to link to
528
529             all_relations: the full set of relations involving this_obj, including ones we don't want
530
531             relation_class: the class that implements the relation from source to dest
532
533             local_attrname: field name representing this_obj in relation_class
534
535             foreign_attrname: field name representing selected_objs in relation_class
536
537             This function will remove all newobjclass relations from this_obj
538             that are not contained in selected_objs, and add any relations that
539             are in selected_objs but don't exist in the data model yet.
540         """
541
542         existing_dest_objs = []
543         for relation in list(all_relations):
544             if getattr(relation, foreign_attrname) not in selected_objs:
545                 #print "deleting site", sdp.site
546                 relation.delete()
547             else:
548                 existing_dest_objs.append(getattr(relation, foreign_attrname))
549
550         for dest_obj in selected_objs:
551             if dest_obj not in existing_dest_objs:
552                 #print "adding site", site
553                 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
554                 relation = relation_class(**kwargs)
555                 relation.save()
556
557     def save(self, commit=True):
558       deployment = super(DeploymentAdminForm, self).save(commit=False)
559
560       if commit:
561         deployment.save()
562
563       if deployment.pk:
564         # save_m2m() doesn't seem to work with 'through' relations. So we
565         #    create/destroy the through models ourselves. There has to be
566         #    a better way...
567
568         self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments_set.all(), SiteDeployments, "deployment", "site")
569         self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments_set.all(), ImageDeployments, "deployment", "image")
570
571         self.save_m2m()
572
573       return deployment
574
575 class DeploymentAdminROForm(DeploymentAdminForm):
576     def save(self, commit=True):
577         raise PermissionDenied
578
579 class SiteAssocInline(PlStackTabularInline):
580     model = Site.deployments.through
581     extra = 0
582     suit_classes = 'suit-tab suit-tab-sites'
583
584 class DeploymentAdmin(PlanetStackBaseAdmin):
585     model = Deployment
586     fieldList = ['name','sites', 'images', 'accessControl']
587     fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
588     inlines = [DeploymentPrivilegeInline,NodeInline,TagInline] # ,ImageDeploymentsInline]
589
590     user_readonly_inlines = [DeploymentPrivilegeROInline,NodeROInline,TagROInline] # ,ImageDeploymentsROInline]
591     user_readonly_fields = ['name']
592
593     suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags')) # ,('imagedeployments','Images'))
594
595     def get_form(self, request, obj=None, **kwargs):
596         if request.user.isReadOnlyUser():
597             kwargs["form"] = DeploymentAdminROForm
598         else:
599             kwargs["form"] = DeploymentAdminForm
600         adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
601
602         # from stackexchange: pass the request object into the form
603
604         class AdminFormMetaClass(adminForm):
605            def __new__(cls, *args, **kwargs):
606                kwargs['request'] = request
607                return adminForm(*args, **kwargs)
608
609         return AdminFormMetaClass
610
611 class ServiceAttrAsTabROInline(ReadOnlyTabularInline):
612     model = ServiceAttribute
613     fields = ['name','value']
614     extra = 0
615     suit_classes = 'suit-tab suit-tab-serviceattrs'
616
617 class ServiceAttrAsTabInline(PlStackTabularInline):
618     model = ServiceAttribute
619     fields = ['name','value']
620     extra = 0
621     suit_classes = 'suit-tab suit-tab-serviceattrs'
622
623 class ServiceAdmin(PlanetStackBaseAdmin):
624     list_display = ("name","description","versionNumber","enabled","published")
625     fieldList = ["name","description","versionNumber","enabled","published"]
626     fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
627     inlines = [ServiceAttrAsTabInline,SliceInline]
628
629     user_readonly_fields = fieldList
630     user_readonly_inlines = [ServiceAttrAsTabROInline,SliceROInline]
631
632     suit_form_tabs =(('general', 'Service Details'),
633         ('slices','Slices'),
634         ('serviceattrs','Additional Attributes'),
635     )
636
637 class SiteAdmin(PlanetStackBaseAdmin):
638     fieldList = ['name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
639     fieldsets = [
640         (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
641         #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
642     ]
643     suit_form_tabs =(('general', 'Site Details'),
644         ('users','Users'),
645         ('siteprivileges','Privileges'),
646         ('deployments','Deployments'),
647         ('slices','Slices'),
648         ('nodes','Nodes'), 
649         ('tags','Tags'),
650     )
651     readonly_fields = ['accountLink']
652
653     user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
654     user_readonly_inlines = [SliceROInline,UserROInline,TagROInline, NodeROInline, SitePrivilegeROInline,SiteDeploymentROInline]
655
656     list_display = ('name', 'login_base','site_url', 'enabled')
657     filter_horizontal = ('deployments',)
658     inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentInline]
659     search_fields = ['name']
660
661     def queryset(self, request):
662         return Site.select_by_user(request.user)
663
664     def get_formsets(self, request, obj=None):
665         for inline in self.get_inline_instances(request, obj):
666             # hide MyInline in the add view
667             if obj is None:
668                 continue
669             if isinstance(inline, SliceInline):
670                 inline.model.caller = request.user
671             yield inline.get_formset(request, obj)
672
673     def get_formsets(self, request, obj=None):
674         for inline in self.get_inline_instances(request, obj):
675             # hide MyInline in the add view
676             if obj is None:
677                 continue
678             if isinstance(inline, SliverInline):
679                 inline.model.caller = request.user
680             yield inline.get_formset(request, obj)
681
682     def accountLink(self, obj):
683         link_obj = obj.accounts.all()
684         if link_obj:
685             reverse_path = "admin:core_account_change"
686             url = reverse(reverse_path, args =(link_obj[0].id,))
687             return "<a href='%s'>%s</a>" % (url, "view billing details")
688         else:
689             return "no billing data for this site"
690     accountLink.allow_tags = True
691     accountLink.short_description = "Billing"
692
693     def save_model(self, request, obj, form, change):
694         # update openstack connection to use this site/tenant
695         obj.save_by_user(request.user) 
696
697     def delete_model(self, request, obj):
698         obj.delete_by_user(request.user)
699         
700
701 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
702     fieldList = ['user', 'site', 'role']
703     fieldsets = [
704         (None, {'fields': fieldList, 'classes':['collapse']})
705     ]
706     list_display = ('user', 'site', 'role')
707     user_readonly_fields = fieldList
708     user_readonly_inlines = []
709
710     def formfield_for_foreignkey(self, db_field, request, **kwargs):
711         if db_field.name == 'site':
712             if not request.user.is_admin:
713                 # only show sites where user is an admin or pi
714                 sites = set()
715                 for site_privilege in SitePrivilege.objects.filer(user=request.user):
716                     if site_privilege.role.role_type in ['admin', 'pi']:
717                         sites.add(site_privilege.site)
718                 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
719
720         if db_field.name == 'user':
721             if not request.user.is_admin:
722                 # only show users from sites where caller has admin or pi role
723                 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
724                 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
725                 sites = [site_privilege.site for site_privilege in site_privileges]
726                 site_privileges = SitePrivilege.objects.filter(site__in=sites)
727                 emails = [site_privilege.user.email for site_privilege in site_privileges]
728                 users = User.objects.filter(email__in=emails)
729                 kwargs['queryset'] = users
730
731         return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
732
733     def queryset(self, request):
734         # admins can see all privileges. Users can only see privileges at sites
735         # where they have the admin role or pi role.
736         qs = super(SitePrivilegeAdmin, self).queryset(request)
737         #if not request.user.is_admin:
738         #    roles = Role.objects.filter(role_type__in=['admin', 'pi'])
739         #    site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
740         #    login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
741         #    sites = Site.objects.filter(login_base__in=login_bases)
742         #    qs = qs.filter(site__in=sites)
743         return qs
744
745 class SliceForm(forms.ModelForm):
746     class Meta:
747         model = Slice
748         widgets = {
749             'service': LinkedSelect 
750         }
751
752 class SliceAdmin(PlanetStackBaseAdmin):
753     form = SliceForm
754     fieldList = ['name', 'site', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
755     fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
756     list_display = ('name', 'site','serviceClass', 'slice_url', 'max_slivers')
757     inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
758
759     user_readonly_fields = fieldList
760     user_readonly_inlines = [SlicePrivilegeROInline,SliverROInline,TagROInline, ReservationROInline, SliceNetworkROInline]
761
762     suit_form_tabs =(('general', 'Slice Details'),
763         ('slicenetworks','Networks'),
764         ('sliceprivileges','Privileges'),
765         ('slivers','Slivers'),
766         ('tags','Tags'),
767         ('reservations','Reservations'),
768     )
769
770     def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
771         #deployment_nodes = {}
772         #for node in Node.objects.all():
773         #    deployment_nodes[node.deployment.id] = get(deployment_nodes, node.deployment.id, []).append( (node.id, node.name) )
774
775         deployment_nodes = []
776         for node in Node.objects.all():
777             deployment_nodes.append( (node.deployment.id, node.id, node.name) )
778
779         context["deployment_nodes"] = deployment_nodes
780
781         return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
782
783     def formfield_for_foreignkey(self, db_field, request, **kwargs):
784         if db_field.name == 'site':
785             kwargs['queryset'] = Site.select_by_user(request.user)
786                 
787         return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
788
789     def queryset(self, request):
790         # admins can see all keys. Users can only see slices they belong to.
791         return Slice.select_by_user(request.user)
792
793     def get_formsets(self, request, obj=None):
794         for inline in self.get_inline_instances(request, obj):
795             # hide MyInline in the add view
796             if obj is None:
797                 continue
798             if isinstance(inline, SliverInline):
799                 inline.model.caller = request.user
800             yield inline.get_formset(request, obj)
801
802
803 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
804     fieldsets = [
805         (None, {'fields': ['user', 'slice', 'role']})
806     ]
807     list_display = ('user', 'slice', 'role')
808
809     user_readonly_fields = ['user', 'slice', 'role']
810     user_readonly_inlines = []
811
812     def formfield_for_foreignkey(self, db_field, request, **kwargs):
813         if db_field.name == 'slice':
814             kwargs['queryset'] = Slice.select_by_user(request.user)
815         
816         if db_field.name == 'user':
817             kwargs['queryset'] = User.select_by_user(request.user)
818
819         return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
820
821     def queryset(self, request):
822         # admins can see all memberships. Users can only see memberships of
823         # slices where they have the admin role.
824         return SlicePrivilege.select_by_user(request.user)
825
826     def save_model(self, request, obj, form, change):
827         # update openstack connection to use this site/tenant
828         auth = request.session.get('auth', {})
829         auth['tenant'] = obj.slice.slicename
830         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
831         obj.save()
832
833     def delete_model(self, request, obj):
834         # update openstack connection to use this site/tenant
835         auth = request.session.get('auth', {})
836         auth['tenant'] = obj.slice.slicename
837         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
838         obj.delete()
839
840
841 class ImageAdmin(PlanetStackBaseAdmin):
842
843     fieldsets = [('Image Details', 
844                    {'fields': ['name', 'disk_format', 'container_format'], 
845                     'classes': ['suit-tab suit-tab-general']})
846                ]
847
848     suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
849
850     inlines = [SliverInline, ImageDeploymentsInline]
851
852     user_readonly_fields = ['name', 'disk_format', 'container_format']
853     user_readonly_inlines = [SliverROInline, ImageDeploymentsROInline]
854     
855 class NodeForm(forms.ModelForm):
856     class Meta:
857         widgets = {
858             'site': LinkedSelect,
859             'deployment': LinkedSelect
860         }
861
862 class NodeAdmin(PlanetStackBaseAdmin):
863     form = NodeForm
864     list_display = ('name', 'site', 'deployment')
865     list_filter = ('deployment',)
866
867     inlines = [TagInline,SliverInline]
868     fieldsets = [('Node Details', {'fields': ['name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
869
870     user_readonly_fields = ['name','site','deployment']
871     user_readonly_inlines = [TagInline,SliverInline]
872
873     suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
874
875
876 class SliverForm(forms.ModelForm):
877     class Meta:
878         model = Sliver
879         ip = forms.CharField(widget=PlainTextWidget)
880         instance_name = forms.CharField(widget=PlainTextWidget)
881         widgets = {
882             'ip': PlainTextWidget(),
883             'instance_name': PlainTextWidget(),
884             'slice': LinkedSelect,
885             'deploymentNetwork': LinkedSelect,
886             'node': LinkedSelect,
887             'image': LinkedSelect
888         }
889
890 class TagAdmin(PlanetStackBaseAdmin):
891     list_display = ['service', 'name', 'value', 'content_type', 'content_object',]
892     user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
893     user_readonly_inlines = []
894
895 class SliverAdmin(PlanetStackBaseAdmin):
896     form = SliverForm
897     fieldsets = [
898         ('Sliver Details', {'fields': ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
899     ]
900     list_display = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']
901
902     suit_form_tabs =(('general', 'Sliver Details'),
903         ('tags','Tags'),
904     )
905
906     inlines = [TagInline]
907
908     user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image']
909     user_readonly_inlines = [TagROInline]
910
911     def formfield_for_foreignkey(self, db_field, request, **kwargs):
912         if db_field.name == 'slice':
913             kwargs['queryset'] = Slice.select_by_user(request.user)
914
915         return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
916
917     def queryset(self, request):
918         # admins can see all slivers. Users can only see slivers of 
919         # the slices they belong to.
920         return Sliver.select_by_user(request.user)
921
922
923     def get_formsets(self, request, obj=None):
924         # make some fields read only if we are updating an existing record
925         if obj == None:
926             #self.readonly_fields = ('ip', 'instance_name') 
927             self.readonly_fields = () 
928         else:
929             self.readonly_fields = () 
930             #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key') 
931
932         for inline in self.get_inline_instances(request, obj):
933             # hide MyInline in the add view
934             if obj is None:
935                 continue
936             if isinstance(inline, SliverInline):
937                 inline.model.caller = request.user
938             yield inline.get_formset(request, obj)
939
940     #def save_model(self, request, obj, form, change):
941     #    # update openstack connection to use this site/tenant
942     #    auth = request.session.get('auth', {})
943     #    auth['tenant'] = obj.slice.name
944     #    obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
945     #    obj.creator = request.user
946     #    obj.save()
947
948     #def delete_model(self, request, obj):
949     #    # update openstack connection to use this site/tenant
950     #    auth = request.session.get('auth', {})
951     #    auth['tenant'] = obj.slice.name
952     #    obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
953     #    obj.delete()
954
955 class UserCreationForm(forms.ModelForm):
956     """A form for creating new users. Includes all the required
957     fields, plus a repeated password."""
958     password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
959     password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
960
961     class Meta:
962         model = User
963         fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
964
965     def clean_password2(self):
966         # Check that the two password entries match
967         password1 = self.cleaned_data.get("password1")
968         password2 = self.cleaned_data.get("password2")
969         if password1 and password2 and password1 != password2:
970             raise forms.ValidationError("Passwords don't match")
971         return password2
972
973     def save(self, commit=True):
974         # Save the provided password in hashed format
975         user = super(UserCreationForm, self).save(commit=False)
976         user.password = self.cleaned_data["password1"]
977         #user.set_password(self.cleaned_data["password1"])
978         if commit:
979             user.save()
980         return user
981
982
983 class UserChangeForm(forms.ModelForm):
984     """A form for updating users. Includes all the fields on
985     the user, but replaces the password field with admin's
986     password hash display field.
987     """
988     password = ReadOnlyPasswordHashField(label='Password',
989                    help_text= '<a href=\"password/\">Change Password</a>.')
990
991     class Meta:
992         model = User
993
994     def clean_password(self):
995         # Regardless of what the user provides, return the initial value.
996         # This is done here, rather than on the field, because the
997         # field does not have access to the initial value
998         return self.initial["password"]
999
1000 class UserDashboardViewInline(PlStackTabularInline):
1001     model = UserDashboardView
1002     extra = 0
1003     suit_classes = 'suit-tab suit-tab-dashboards'
1004     fields = ['user', 'dashboardView', 'order']
1005
1006 class UserDashboardViewROInline(ReadOnlyTabularInline):
1007     model = UserDashboardView
1008     extra = 0
1009     suit_classes = 'suit-tab suit-tab-dashboards'
1010     fields = ['user', 'dashboardView', 'order']
1011
1012 class UserAdmin(UserAdmin):
1013     class Meta:
1014         app_label = "core"
1015
1016     # The forms to add and change user instances
1017     form = UserChangeForm
1018     add_form = UserCreationForm
1019
1020     # The fields to be used in displaying the User model.
1021     # These override the definitions on the base UserAdmin
1022     # that reference specific fields on auth.User.
1023     list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
1024     #list_display = ('email', 'username','firstname', 'lastname', 'is_admin', 'last_login')
1025     list_filter = ('site',)
1026     inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
1027
1028     fieldListLoginDetails = ['email','site','password','is_readonly','is_amin','public_key']
1029     fieldListContactInfo = ['firstname','lastname','phone','timezone']
1030
1031     fieldsets = (
1032         ('Login Details', {'fields': ['email', 'site','password', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1033         ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1034         #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1035         #('Important dates', {'fields': ('last_login',)}),
1036     )
1037     add_fieldsets = (
1038         (None, {
1039             'classes': ('wide',),
1040             'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
1041         ),
1042     )
1043     search_fields = ('email',)
1044     ordering = ('email',)
1045     filter_horizontal = ()
1046
1047     user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1048     user_readonly_inlines = [SlicePrivilegeROInline,SitePrivilegeROInline,DeploymentPrivilegeROInline,UserDashboardViewROInline]
1049
1050     suit_form_tabs =(('general','Login Details'),
1051                      ('contact','Contact Information'),
1052                      ('sliceprivileges','Slice Privileges'),
1053                      ('siteprivileges','Site Privileges'),
1054                      ('deploymentprivileges','Deployment Privileges'),
1055                      ('dashboards','Dashboard Views'))
1056
1057     def formfield_for_foreignkey(self, db_field, request, **kwargs):
1058         if db_field.name == 'site':
1059             kwargs['queryset'] = Site.select_by_user(request.user)
1060
1061         return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1062
1063     def has_add_permission(self, request, obj=None):
1064         return (not self.__user_is_readonly(request))
1065
1066     def has_delete_permission(self, request, obj=None):
1067         return (not self.__user_is_readonly(request))
1068
1069     def get_actions(self,request):
1070         actions = super(UserAdmin,self).get_actions(request)
1071
1072         if self.__user_is_readonly(request):
1073             if 'delete_selected' in actions:
1074                 del actions['delete_selected']
1075
1076         return actions
1077
1078     def change_view(self,request,object_id, extra_context=None):
1079
1080         if self.__user_is_readonly(request):
1081             if not hasattr(self, "readonly_save"):
1082                 # save the original readonly fields\r
1083                 self.readonly_save = self.readonly_fields\r
1084                 self.inlines_save = self.inlines
1085             self.readonly_fields=self.user_readonly_fields
1086             self.inlines = self.user_readonly_inlines
1087         else:
1088             if hasattr(self, "readonly_save"):\r
1089                 # restore the original readonly fields\r
1090                 self.readonly_fields = self.readonly_save\r
1091                 self.inlines = self.inlines_save
1092
1093         try:
1094             return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1095         except PermissionDenied:
1096             pass
1097         if request.method == 'POST':
1098             raise PermissionDenied
1099         request.readonly = True
1100         return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1101
1102     def __user_is_readonly(self, request):
1103         #groups = [x.name for x in request.user.groups.all() ]
1104         #return "readonly" in groups
1105         return request.user.isReadOnlyUser()
1106
1107     def queryset(self, request):
1108         return User.select_by_user(request.user)
1109
1110 class DashboardViewAdmin(PlanetStackBaseAdmin):
1111     fieldsets = [('Dashboard View Details',
1112                    {'fields': ['name', 'url'],
1113                     'classes': ['suit-tab suit-tab-general']})
1114                ]
1115
1116     suit_form_tabs =(('general','Dashboard View Details'),)
1117
1118 class ServiceResourceROInline(ReadOnlyTabularInline):
1119     model = ServiceResource
1120     extra = 0
1121     fields = ['serviceClass', 'name', 'maxUnitsDeployment', 'maxUnitsNode', 'maxDuration', 'bucketInRate', 'bucketMaxSize', 'cost', 'calendarReservable']
1122
1123 class ServiceResourceInline(PlStackTabularInline):
1124     model = ServiceResource
1125     extra = 0
1126
1127 class ServiceClassAdmin(PlanetStackBaseAdmin):
1128     list_display = ('name', 'commitment', 'membershipFee')
1129     inlines = [ServiceResourceInline]
1130
1131     user_readonly_fields = ['name', 'commitment', 'membershipFee']
1132     user_readonly_inlines = []
1133
1134 class ReservedResourceROInline(ReadOnlyTabularInline):
1135     model = ReservedResource
1136     extra = 0
1137     fields = ['sliver', 'resource','quantity','reservationSet']
1138     suit_classes = 'suit-tab suit-tab-reservedresources'
1139
1140 class ReservedResourceInline(PlStackTabularInline):
1141     model = ReservedResource
1142     extra = 0
1143     suit_classes = 'suit-tab suit-tab-reservedresources'
1144
1145     def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1146         field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1147
1148         if db_field.name == 'resource':
1149             # restrict resources to those that the slice's service class allows
1150             if request._slice is not None:
1151                 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1152                 if len(field.queryset) > 0:
1153                     field.initial = field.queryset.all()[0]
1154             else:\r
1155                 field.queryset = field.queryset.none()\r
1156         elif db_field.name == 'sliver':\r
1157             # restrict slivers to those that belong to the slice\r
1158             if request._slice is not None:\r
1159                 field.queryset = field.queryset.filter(slice = request._slice)
1160             else:
1161                 field.queryset = field.queryset.none()\r
1162 \r
1163         return field
1164
1165     def queryset(self, request):
1166         return ReservedResource.select_by_user(request.user)
1167
1168 class ReservationChangeForm(forms.ModelForm):
1169     class Meta:
1170         model = Reservation
1171         widgets = {
1172             'slice' : LinkedSelect
1173         }
1174
1175 class ReservationAddForm(forms.ModelForm):
1176     slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1177     refresh = forms.CharField(widget=forms.HiddenInput())
1178
1179     class Media:
1180        css = {'all': ('planetstack.css',)}   # .field-refresh { display: none; }
1181
1182     def clean_slice(self):
1183         slice = self.cleaned_data.get("slice")
1184         x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1185         if len(x) == 0:
1186             raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1187         return slice
1188
1189     class Meta:
1190         model = Reservation
1191         widgets = {
1192             'slice' : LinkedSelect
1193         }
1194
1195
1196 class ReservationAddRefreshForm(ReservationAddForm):
1197     """ This form is displayed when the Reservation Form receives an update
1198         from the Slice dropdown onChange handler. It doesn't validate the
1199         data and doesn't save the data. This will cause the form to be
1200         redrawn.
1201     """
1202
1203     """ don't validate anything other than slice """
1204     dont_validate_fields = ("startTime", "duration")
1205
1206     def full_clean(self):
1207         result = super(ReservationAddForm, self).full_clean()
1208
1209         for fieldname in self.dont_validate_fields:
1210             if fieldname in self._errors:
1211                 del self._errors[fieldname]
1212
1213         return result
1214
1215     """ don't save anything """
1216     def is_valid(self):
1217         return False
1218
1219 class ReservationAdmin(PlanetStackBaseAdmin):
1220     fieldList = ['slice', 'startTime', 'duration']
1221     fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1222     list_display = ('startTime', 'duration')
1223     form = ReservationAddForm
1224
1225     suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1226
1227     inlines = [ReservedResourceInline]
1228     user_readonly_inlines = [ReservedResourceROInline]
1229     user_readonly_fields = fieldList
1230
1231     def add_view(self, request, form_url='', extra_context=None):
1232         timezone.activate(request.user.timezone)
1233         request._refresh = False
1234         request._slice = None
1235         if request.method == 'POST':
1236             # "refresh" will be set to "1" if the form was submitted due to
1237             # a change in the Slice dropdown.
1238             if request.POST.get("refresh","1") == "1":
1239                 request._refresh = True
1240                 request.POST["refresh"] = "0"
1241
1242             # Keep track of the slice that was selected, so the
1243             # reservedResource inline can filter items for the slice.
1244             request._slice = request.POST.get("slice",None)
1245             if (request._slice is not None):
1246                 request._slice = Slice.objects.get(id=request._slice)
1247
1248         result =  super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1249         return result
1250
1251     def changelist_view(self, request, extra_context = None):
1252         timezone.activate(request.user.timezone)
1253         return super(ReservationAdmin, self).changelist_view(request, extra_context)
1254
1255     def get_form(self, request, obj=None, **kwargs):
1256         request._obj_ = obj
1257         if obj is not None:
1258             # For changes, set request._slice to the slice already set in the
1259             # object.
1260             request._slice = obj.slice
1261             self.form = ReservationChangeForm
1262         else:
1263             if getattr(request, "_refresh", False):
1264                 self.form = ReservationAddRefreshForm
1265             else:
1266                 self.form = ReservationAddForm
1267         return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1268
1269     def get_readonly_fields(self, request, obj=None):
1270         if (obj is not None):
1271             # Prevent slice from being changed after the reservation has been
1272             # created.
1273             return ['slice']
1274         else:
1275             return []
1276
1277     def queryset(self, request):
1278         return Reservation.select_by_user(request.user)
1279
1280 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1281     list_display = ("name", )
1282     user_readonly_fields = ['name']
1283     user_readonly_inlines = []
1284
1285 class RouterAdmin(PlanetStackBaseAdmin):
1286     list_display = ("name", )
1287     user_readonly_fields = ['name']
1288     user_readonly_inlines = []
1289
1290 class RouterROInline(ReadOnlyTabularInline):
1291     model = Router.networks.through
1292     extra = 0
1293     verbose_name_plural = "Routers"
1294     verbose_name = "Router"
1295     suit_classes = 'suit-tab suit-tab-routers'
1296
1297     fields = ['name', 'owner', 'permittedNetworks', 'networks']
1298
1299 class RouterInline(PlStackTabularInline):
1300     model = Router.networks.through
1301     extra = 0
1302     verbose_name_plural = "Routers"
1303     verbose_name = "Router"
1304     suit_classes = 'suit-tab suit-tab-routers'
1305
1306 class NetworkParameterROInline(ReadOnlyTabularInline):
1307     model = NetworkParameter
1308     extra = 1
1309     verbose_name_plural = "Parameters"
1310     verbose_name = "Parameter"
1311     suit_classes = 'suit-tab suit-tab-netparams'
1312     fields = ['parameter', 'value', 'content_type', 'object_id', 'content_object']
1313
1314 class NetworkParameterInline(generic.GenericTabularInline):
1315     model = NetworkParameter
1316     extra = 1
1317     verbose_name_plural = "Parameters"
1318     verbose_name = "Parameter"
1319     suit_classes = 'suit-tab suit-tab-netparams'
1320
1321 class NetworkSliversROInline(ReadOnlyTabularInline):
1322     fields = ['network', 'sliver', 'ip', 'port_id']
1323     model = NetworkSliver
1324     extra = 0
1325     verbose_name_plural = "Slivers"
1326     verbose_name = "Sliver"
1327     suit_classes = 'suit-tab suit-tab-networkslivers'
1328
1329 class NetworkSliversInline(PlStackTabularInline):
1330     readonly_fields = ("ip", )
1331     model = NetworkSliver
1332     selflink_fieldname = "sliver"
1333     extra = 0
1334     verbose_name_plural = "Slivers"
1335     verbose_name = "Sliver"
1336     suit_classes = 'suit-tab suit-tab-networkslivers'
1337
1338 class NetworkSlicesROInline(ReadOnlyTabularInline):
1339     model = NetworkSlice
1340     extra = 0
1341     verbose_name_plural = "Slices"
1342     verbose_name = "Slice"
1343     suit_classes = 'suit-tab suit-tab-networkslices'
1344     fields = ['network','slice']
1345
1346 class NetworkSlicesInline(PlStackTabularInline):
1347     model = NetworkSlice
1348     selflink_fieldname = "slice"
1349     extra = 0
1350     verbose_name_plural = "Slices"
1351     verbose_name = "Slice"
1352     suit_classes = 'suit-tab suit-tab-networkslices'
1353
1354 class NetworkAdmin(PlanetStackBaseAdmin):
1355     list_display = ("name", "subnet", "ports", "labels")
1356     readonly_fields = ("subnet", )
1357
1358     inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1359
1360     fieldsets = [
1361         (None, {'fields': ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet'], 'classes':['suit-tab suit-tab-general']}),]
1362
1363     user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1364     user_readonly_inlines = [NetworkParameterROInline, NetworkSliversROInline, NetworkSlicesROInline, RouterROInline]
1365
1366     suit_form_tabs =(
1367         ('general','Network Details'),
1368         ('netparams', 'Parameters'),
1369         ('networkslivers','Slivers'),
1370         ('networkslices','Slices'),
1371         ('routers','Routers'),
1372     )
1373 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1374     list_display = ("name", "guaranteedBandwidth", "visibility")
1375     user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1376     user_readonly_inlines = []
1377
1378 # register a signal that caches the user's credentials when they log in
1379 def cache_credentials(sender, user, request, **kwds):
1380     auth = {'username': request.POST['username'],
1381             'password': request.POST['password']}
1382     request.session['auth'] = auth
1383 user_logged_in.connect(cache_credentials)
1384
1385 def dollar_field(fieldName, short_description):
1386     def newFunc(self, obj):
1387         try:
1388             x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1389         except:
1390             x=getattr(obj, fieldName, 0.0)
1391         return x
1392     newFunc.short_description = short_description
1393     return newFunc
1394
1395 def right_dollar_field(fieldName, short_description):
1396     def newFunc(self, obj):
1397         try:
1398             #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1399             x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1400         except:
1401             x=getattr(obj, fieldName, 0.0)
1402         return x
1403     newFunc.short_description = short_description
1404     newFunc.allow_tags = True
1405     return newFunc
1406
1407 class InvoiceChargeInline(PlStackTabularInline):
1408     model = Charge
1409     extra = 0
1410     verbose_name_plural = "Charges"
1411     verbose_name = "Charge"
1412     exclude = ['account']
1413     fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1414     readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1415     can_delete = False
1416     max_num = 0
1417
1418     dollar_amount = right_dollar_field("amount", "Amount")
1419
1420 class InvoiceAdmin(admin.ModelAdmin):
1421     list_display = ("date", "account")
1422
1423     inlines = [InvoiceChargeInline]
1424
1425     fields = ["date", "account", "dollar_amount"]
1426     readonly_fields = ["date", "account", "dollar_amount"]
1427
1428     dollar_amount = dollar_field("amount", "Amount")
1429
1430 class InvoiceInline(PlStackTabularInline):
1431     model = Invoice
1432     extra = 0
1433     verbose_name_plural = "Invoices"
1434     verbose_name = "Invoice"
1435     fields = ["date", "dollar_amount"]
1436     readonly_fields = ["date", "dollar_amount"]
1437     suit_classes = 'suit-tab suit-tab-accountinvoice'
1438     can_delete=False
1439     max_num=0
1440
1441     dollar_amount = right_dollar_field("amount", "Amount")
1442
1443 class PendingChargeInline(PlStackTabularInline):
1444     model = Charge
1445     extra = 0
1446     verbose_name_plural = "Charges"
1447     verbose_name = "Charge"
1448     exclude = ["invoice"]
1449     fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1450     readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1451     suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1452     can_delete=False
1453     max_num=0
1454
1455     def queryset(self, request):
1456         qs = super(PendingChargeInline, self).queryset(request)
1457         qs = qs.filter(state="pending")
1458         return qs
1459
1460     dollar_amount = right_dollar_field("amount", "Amount")
1461
1462 class PaymentInline(PlStackTabularInline):
1463     model=Payment
1464     extra = 1
1465     verbose_name_plural = "Payments"
1466     verbose_name = "Payment"
1467     fields = ["date", "dollar_amount"]
1468     readonly_fields = ["date", "dollar_amount"]
1469     suit_classes = 'suit-tab suit-tab-accountpayments'
1470     can_delete=False
1471     max_num=0
1472
1473     dollar_amount = right_dollar_field("amount", "Amount")
1474
1475 class AccountAdmin(admin.ModelAdmin):
1476     list_display = ("site", "balance_due")
1477
1478     inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1479
1480     fieldsets = [
1481         (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1482
1483     readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1484
1485     suit_form_tabs =(
1486         ('general','Account Details'),
1487         ('accountinvoice', 'Invoices'),
1488         ('accountpayments', 'Payments'),
1489         ('accountpendingcharges','Pending Charges'),
1490     )
1491
1492     dollar_balance_due = dollar_field("balance_due", "Balance Due")
1493     dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1494     dollar_total_payments = dollar_field("total_payments", "Total Payments")
1495
1496
1497 # Now register the new UserAdmin...
1498 admin.site.register(User, UserAdmin)
1499 # ... and, since we're not using Django's builtin permissions,
1500 # unregister the Group model from admin.
1501 #admin.site.unregister(Group)
1502
1503 #Do not show django evolution in the admin interface
1504 from django_evolution.models import Version, Evolution
1505 #admin.site.unregister(Version)
1506 #admin.site.unregister(Evolution)
1507
1508
1509 # When debugging it is often easier to see all the classes, but for regular use 
1510 # only the top-levels should be displayed
1511 showAll = False
1512
1513 admin.site.register(Deployment, DeploymentAdmin)
1514 admin.site.register(Site, SiteAdmin)
1515 admin.site.register(Slice, SliceAdmin)
1516 admin.site.register(Service, ServiceAdmin)
1517 admin.site.register(Reservation, ReservationAdmin)
1518 admin.site.register(Network, NetworkAdmin)
1519 admin.site.register(Router, RouterAdmin)
1520 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1521 admin.site.register(Account, AccountAdmin)
1522 admin.site.register(Invoice, InvoiceAdmin)
1523
1524 if True:
1525     admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1526     admin.site.register(ServiceClass, ServiceClassAdmin)
1527     #admin.site.register(PlanetStack)
1528     admin.site.register(Tag, TagAdmin)
1529     admin.site.register(DeploymentRole)
1530     admin.site.register(SiteRole)
1531     admin.site.register(SliceRole)
1532     admin.site.register(PlanetStackRole)
1533     admin.site.register(Node, NodeAdmin)
1534     #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1535     #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1536     admin.site.register(Sliver, SliverAdmin)
1537     admin.site.register(Image, ImageAdmin)
1538     admin.site.register(DashboardView, DashboardViewAdmin)
1539