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