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