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