expose is_active bit on user page
[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_filter = ('site',)
1025     inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
1026
1027     fieldListLoginDetails = ['email','site','password','is_active','is_readonly','is_admin','public_key']
1028     fieldListContactInfo = ['firstname','lastname','phone','timezone']
1029
1030     fieldsets = (
1031         ('Login Details', {'fields': ['email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1032         ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1033         #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1034         #('Important dates', {'fields': ('last_login',)}),
1035     )
1036     add_fieldsets = (
1037         (None, {
1038             'classes': ('wide',),
1039             'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
1040         ),
1041     )
1042     search_fields = ('email',)
1043     ordering = ('email',)
1044     filter_horizontal = ()
1045
1046     user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1047     user_readonly_inlines = [SlicePrivilegeROInline,SitePrivilegeROInline,DeploymentPrivilegeROInline,UserDashboardViewROInline]
1048
1049     suit_form_tabs =(('general','Login Details'),
1050                      ('contact','Contact Information'),
1051                      ('sliceprivileges','Slice Privileges'),
1052                      ('siteprivileges','Site Privileges'),
1053                      ('deploymentprivileges','Deployment Privileges'),
1054                      ('dashboards','Dashboard Views'))
1055
1056     def formfield_for_foreignkey(self, db_field, request, **kwargs):
1057         if db_field.name == 'site':
1058             kwargs['queryset'] = Site.select_by_user(request.user)
1059
1060         return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1061
1062     def has_add_permission(self, request, obj=None):
1063         return (not self.__user_is_readonly(request))
1064
1065     def has_delete_permission(self, request, obj=None):
1066         return (not self.__user_is_readonly(request))
1067
1068     def get_actions(self,request):
1069         actions = super(UserAdmin,self).get_actions(request)
1070
1071         if self.__user_is_readonly(request):
1072             if 'delete_selected' in actions:
1073                 del actions['delete_selected']
1074
1075         return actions
1076
1077     def change_view(self,request,object_id, extra_context=None):
1078
1079         if self.__user_is_readonly(request):
1080             if not hasattr(self, "readonly_save"):
1081                 # save the original readonly fields\r
1082                 self.readonly_save = self.readonly_fields\r
1083                 self.inlines_save = self.inlines
1084             self.readonly_fields=self.user_readonly_fields
1085             self.inlines = self.user_readonly_inlines
1086         else:
1087             if hasattr(self, "readonly_save"):\r
1088                 # restore the original readonly fields\r
1089                 self.readonly_fields = self.readonly_save\r
1090                 self.inlines = self.inlines_save
1091
1092         try:
1093             return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1094         except PermissionDenied:
1095             pass
1096         if request.method == 'POST':
1097             raise PermissionDenied
1098         request.readonly = True
1099         return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1100
1101     def __user_is_readonly(self, request):
1102         #groups = [x.name for x in request.user.groups.all() ]
1103         #return "readonly" in groups
1104         return request.user.isReadOnlyUser()
1105
1106     def queryset(self, request):
1107         return User.select_by_user(request.user)
1108
1109 class DashboardViewAdmin(PlanetStackBaseAdmin):
1110     fieldsets = [('Dashboard View Details',
1111                    {'fields': ['name', 'url'],
1112                     'classes': ['suit-tab suit-tab-general']})
1113                ]
1114
1115     suit_form_tabs =(('general','Dashboard View Details'),)
1116
1117 class ServiceResourceROInline(ReadOnlyTabularInline):
1118     model = ServiceResource
1119     extra = 0
1120     fields = ['serviceClass', 'name', 'maxUnitsDeployment', 'maxUnitsNode', 'maxDuration', 'bucketInRate', 'bucketMaxSize', 'cost', 'calendarReservable']
1121
1122 class ServiceResourceInline(PlStackTabularInline):
1123     model = ServiceResource
1124     extra = 0
1125
1126 class ServiceClassAdmin(PlanetStackBaseAdmin):
1127     list_display = ('name', 'commitment', 'membershipFee')
1128     inlines = [ServiceResourceInline]
1129
1130     user_readonly_fields = ['name', 'commitment', 'membershipFee']
1131     user_readonly_inlines = []
1132
1133 class ReservedResourceROInline(ReadOnlyTabularInline):
1134     model = ReservedResource
1135     extra = 0
1136     fields = ['sliver', 'resource','quantity','reservationSet']
1137     suit_classes = 'suit-tab suit-tab-reservedresources'
1138
1139 class ReservedResourceInline(PlStackTabularInline):
1140     model = ReservedResource
1141     extra = 0
1142     suit_classes = 'suit-tab suit-tab-reservedresources'
1143
1144     def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1145         field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1146
1147         if db_field.name == 'resource':
1148             # restrict resources to those that the slice's service class allows
1149             if request._slice is not None:
1150                 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1151                 if len(field.queryset) > 0:
1152                     field.initial = field.queryset.all()[0]
1153             else:\r
1154                 field.queryset = field.queryset.none()\r
1155         elif db_field.name == 'sliver':\r
1156             # restrict slivers to those that belong to the slice\r
1157             if request._slice is not None:\r
1158                 field.queryset = field.queryset.filter(slice = request._slice)
1159             else:
1160                 field.queryset = field.queryset.none()\r
1161 \r
1162         return field
1163
1164     def queryset(self, request):
1165         return ReservedResource.select_by_user(request.user)
1166
1167 class ReservationChangeForm(forms.ModelForm):
1168     class Meta:
1169         model = Reservation
1170         widgets = {
1171             'slice' : LinkedSelect
1172         }
1173
1174 class ReservationAddForm(forms.ModelForm):
1175     slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1176     refresh = forms.CharField(widget=forms.HiddenInput())
1177
1178     class Media:
1179        css = {'all': ('planetstack.css',)}   # .field-refresh { display: none; }
1180
1181     def clean_slice(self):
1182         slice = self.cleaned_data.get("slice")
1183         x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1184         if len(x) == 0:
1185             raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1186         return slice
1187
1188     class Meta:
1189         model = Reservation
1190         widgets = {
1191             'slice' : LinkedSelect
1192         }
1193
1194
1195 class ReservationAddRefreshForm(ReservationAddForm):
1196     """ This form is displayed when the Reservation Form receives an update
1197         from the Slice dropdown onChange handler. It doesn't validate the
1198         data and doesn't save the data. This will cause the form to be
1199         redrawn.
1200     """
1201
1202     """ don't validate anything other than slice """
1203     dont_validate_fields = ("startTime", "duration")
1204
1205     def full_clean(self):
1206         result = super(ReservationAddForm, self).full_clean()
1207
1208         for fieldname in self.dont_validate_fields:
1209             if fieldname in self._errors:
1210                 del self._errors[fieldname]
1211
1212         return result
1213
1214     """ don't save anything """
1215     def is_valid(self):
1216         return False
1217
1218 class ReservationAdmin(PlanetStackBaseAdmin):
1219     fieldList = ['slice', 'startTime', 'duration']
1220     fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1221     list_display = ('startTime', 'duration')
1222     form = ReservationAddForm
1223
1224     suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1225
1226     inlines = [ReservedResourceInline]
1227     user_readonly_inlines = [ReservedResourceROInline]
1228     user_readonly_fields = fieldList
1229
1230     def add_view(self, request, form_url='', extra_context=None):
1231         timezone.activate(request.user.timezone)
1232         request._refresh = False
1233         request._slice = None
1234         if request.method == 'POST':
1235             # "refresh" will be set to "1" if the form was submitted due to
1236             # a change in the Slice dropdown.
1237             if request.POST.get("refresh","1") == "1":
1238                 request._refresh = True
1239                 request.POST["refresh"] = "0"
1240
1241             # Keep track of the slice that was selected, so the
1242             # reservedResource inline can filter items for the slice.
1243             request._slice = request.POST.get("slice",None)
1244             if (request._slice is not None):
1245                 request._slice = Slice.objects.get(id=request._slice)
1246
1247         result =  super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1248         return result
1249
1250     def changelist_view(self, request, extra_context = None):
1251         timezone.activate(request.user.timezone)
1252         return super(ReservationAdmin, self).changelist_view(request, extra_context)
1253
1254     def get_form(self, request, obj=None, **kwargs):
1255         request._obj_ = obj
1256         if obj is not None:
1257             # For changes, set request._slice to the slice already set in the
1258             # object.
1259             request._slice = obj.slice
1260             self.form = ReservationChangeForm
1261         else:
1262             if getattr(request, "_refresh", False):
1263                 self.form = ReservationAddRefreshForm
1264             else:
1265                 self.form = ReservationAddForm
1266         return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1267
1268     def get_readonly_fields(self, request, obj=None):
1269         if (obj is not None):
1270             # Prevent slice from being changed after the reservation has been
1271             # created.
1272             return ['slice']
1273         else:
1274             return []
1275
1276     def queryset(self, request):
1277         return Reservation.select_by_user(request.user)
1278
1279 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1280     list_display = ("name", )
1281     user_readonly_fields = ['name']
1282     user_readonly_inlines = []
1283
1284 class RouterAdmin(PlanetStackBaseAdmin):
1285     list_display = ("name", )
1286     user_readonly_fields = ['name']
1287     user_readonly_inlines = []
1288
1289 class RouterROInline(ReadOnlyTabularInline):
1290     model = Router.networks.through
1291     extra = 0
1292     verbose_name_plural = "Routers"
1293     verbose_name = "Router"
1294     suit_classes = 'suit-tab suit-tab-routers'
1295
1296     fields = ['name', 'owner', 'permittedNetworks', 'networks']
1297
1298 class RouterInline(PlStackTabularInline):
1299     model = Router.networks.through
1300     extra = 0
1301     verbose_name_plural = "Routers"
1302     verbose_name = "Router"
1303     suit_classes = 'suit-tab suit-tab-routers'
1304
1305 class NetworkParameterROInline(ReadOnlyTabularInline):
1306     model = NetworkParameter
1307     extra = 1
1308     verbose_name_plural = "Parameters"
1309     verbose_name = "Parameter"
1310     suit_classes = 'suit-tab suit-tab-netparams'
1311     fields = ['parameter', 'value', 'content_type', 'object_id', 'content_object']
1312
1313 class NetworkParameterInline(generic.GenericTabularInline):
1314     model = NetworkParameter
1315     extra = 1
1316     verbose_name_plural = "Parameters"
1317     verbose_name = "Parameter"
1318     suit_classes = 'suit-tab suit-tab-netparams'
1319
1320 class NetworkSliversROInline(ReadOnlyTabularInline):
1321     fields = ['network', 'sliver', 'ip', 'port_id']
1322     model = NetworkSliver
1323     extra = 0
1324     verbose_name_plural = "Slivers"
1325     verbose_name = "Sliver"
1326     suit_classes = 'suit-tab suit-tab-networkslivers'
1327
1328 class NetworkSliversInline(PlStackTabularInline):
1329     readonly_fields = ("ip", )
1330     model = NetworkSliver
1331     selflink_fieldname = "sliver"
1332     extra = 0
1333     verbose_name_plural = "Slivers"
1334     verbose_name = "Sliver"
1335     suit_classes = 'suit-tab suit-tab-networkslivers'
1336
1337 class NetworkSlicesROInline(ReadOnlyTabularInline):
1338     model = NetworkSlice
1339     extra = 0
1340     verbose_name_plural = "Slices"
1341     verbose_name = "Slice"
1342     suit_classes = 'suit-tab suit-tab-networkslices'
1343     fields = ['network','slice']
1344
1345 class NetworkSlicesInline(PlStackTabularInline):
1346     model = NetworkSlice
1347     selflink_fieldname = "slice"
1348     extra = 0
1349     verbose_name_plural = "Slices"
1350     verbose_name = "Slice"
1351     suit_classes = 'suit-tab suit-tab-networkslices'
1352
1353 class NetworkAdmin(PlanetStackBaseAdmin):
1354     list_display = ("name", "subnet", "ports", "labels")
1355     readonly_fields = ("subnet", )
1356
1357     inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1358
1359     fieldsets = [
1360         (None, {'fields': ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet'], 'classes':['suit-tab suit-tab-general']}),]
1361
1362     user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1363     user_readonly_inlines = [NetworkParameterROInline, NetworkSliversROInline, NetworkSlicesROInline, RouterROInline]
1364
1365     suit_form_tabs =(
1366         ('general','Network Details'),
1367         ('netparams', 'Parameters'),
1368         ('networkslivers','Slivers'),
1369         ('networkslices','Slices'),
1370         ('routers','Routers'),
1371     )
1372 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1373     list_display = ("name", "guaranteedBandwidth", "visibility")
1374     user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1375     user_readonly_inlines = []
1376
1377 # register a signal that caches the user's credentials when they log in
1378 def cache_credentials(sender, user, request, **kwds):
1379     auth = {'username': request.POST['username'],
1380             'password': request.POST['password']}
1381     request.session['auth'] = auth
1382 user_logged_in.connect(cache_credentials)
1383
1384 def dollar_field(fieldName, short_description):
1385     def newFunc(self, obj):
1386         try:
1387             x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1388         except:
1389             x=getattr(obj, fieldName, 0.0)
1390         return x
1391     newFunc.short_description = short_description
1392     return newFunc
1393
1394 def right_dollar_field(fieldName, short_description):
1395     def newFunc(self, obj):
1396         try:
1397             #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1398             x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1399         except:
1400             x=getattr(obj, fieldName, 0.0)
1401         return x
1402     newFunc.short_description = short_description
1403     newFunc.allow_tags = True
1404     return newFunc
1405
1406 class InvoiceChargeInline(PlStackTabularInline):
1407     model = Charge
1408     extra = 0
1409     verbose_name_plural = "Charges"
1410     verbose_name = "Charge"
1411     exclude = ['account']
1412     fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1413     readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1414     can_delete = False
1415     max_num = 0
1416
1417     dollar_amount = right_dollar_field("amount", "Amount")
1418
1419 class InvoiceAdmin(admin.ModelAdmin):
1420     list_display = ("date", "account")
1421
1422     inlines = [InvoiceChargeInline]
1423
1424     fields = ["date", "account", "dollar_amount"]
1425     readonly_fields = ["date", "account", "dollar_amount"]
1426
1427     dollar_amount = dollar_field("amount", "Amount")
1428
1429 class InvoiceInline(PlStackTabularInline):
1430     model = Invoice
1431     extra = 0
1432     verbose_name_plural = "Invoices"
1433     verbose_name = "Invoice"
1434     fields = ["date", "dollar_amount"]
1435     readonly_fields = ["date", "dollar_amount"]
1436     suit_classes = 'suit-tab suit-tab-accountinvoice'
1437     can_delete=False
1438     max_num=0
1439
1440     dollar_amount = right_dollar_field("amount", "Amount")
1441
1442 class PendingChargeInline(PlStackTabularInline):
1443     model = Charge
1444     extra = 0
1445     verbose_name_plural = "Charges"
1446     verbose_name = "Charge"
1447     exclude = ["invoice"]
1448     fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1449     readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1450     suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1451     can_delete=False
1452     max_num=0
1453
1454     def queryset(self, request):
1455         qs = super(PendingChargeInline, self).queryset(request)
1456         qs = qs.filter(state="pending")
1457         return qs
1458
1459     dollar_amount = right_dollar_field("amount", "Amount")
1460
1461 class PaymentInline(PlStackTabularInline):
1462     model=Payment
1463     extra = 1
1464     verbose_name_plural = "Payments"
1465     verbose_name = "Payment"
1466     fields = ["date", "dollar_amount"]
1467     readonly_fields = ["date", "dollar_amount"]
1468     suit_classes = 'suit-tab suit-tab-accountpayments'
1469     can_delete=False
1470     max_num=0
1471
1472     dollar_amount = right_dollar_field("amount", "Amount")
1473
1474 class AccountAdmin(admin.ModelAdmin):
1475     list_display = ("site", "balance_due")
1476
1477     inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1478
1479     fieldsets = [
1480         (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1481
1482     readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1483
1484     suit_form_tabs =(
1485         ('general','Account Details'),
1486         ('accountinvoice', 'Invoices'),
1487         ('accountpayments', 'Payments'),
1488         ('accountpendingcharges','Pending Charges'),
1489     )
1490
1491     dollar_balance_due = dollar_field("balance_due", "Balance Due")
1492     dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1493     dollar_total_payments = dollar_field("total_payments", "Total Payments")
1494
1495
1496 # Now register the new UserAdmin...
1497 admin.site.register(User, UserAdmin)
1498 # ... and, since we're not using Django's builtin permissions,
1499 # unregister the Group model from admin.
1500 #admin.site.unregister(Group)
1501
1502 #Do not show django evolution in the admin interface
1503 from django_evolution.models import Version, Evolution
1504 #admin.site.unregister(Version)
1505 #admin.site.unregister(Evolution)
1506
1507
1508 # When debugging it is often easier to see all the classes, but for regular use 
1509 # only the top-levels should be displayed
1510 showAll = False
1511
1512 admin.site.register(Deployment, DeploymentAdmin)
1513 admin.site.register(Site, SiteAdmin)
1514 admin.site.register(Slice, SliceAdmin)
1515 admin.site.register(Service, ServiceAdmin)
1516 admin.site.register(Reservation, ReservationAdmin)
1517 admin.site.register(Network, NetworkAdmin)
1518 admin.site.register(Router, RouterAdmin)
1519 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1520 admin.site.register(Account, AccountAdmin)
1521 admin.site.register(Invoice, InvoiceAdmin)
1522
1523 if True:
1524     admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1525     admin.site.register(ServiceClass, ServiceClassAdmin)
1526     #admin.site.register(PlanetStack)
1527     admin.site.register(Tag, TagAdmin)
1528     admin.site.register(DeploymentRole)
1529     admin.site.register(SiteRole)
1530     admin.site.register(SliceRole)
1531     admin.site.register(PlanetStackRole)
1532     admin.site.register(Node, NodeAdmin)
1533     #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1534     #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1535     admin.site.register(Sliver, SliverAdmin)
1536     admin.site.register(Image, ImageAdmin)
1537     admin.site.register(DashboardView, DashboardViewAdmin)
1538