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