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