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