Merge branch 'master' of ssh://git.planet-lab.org/git/plstackapi
[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         # update openstack connection to use this site/tenant
426         obj.save_by_user(request.user)
427
428     def delete_model(self, request, obj):
429         obj.delete_by_user(request.user)
430
431     def save_formset(self, request, form, formset, change):
432         instances = formset.save(commit=False)
433         for instance in instances:
434             instance.save_by_user(request.user)
435         formset.save_m2m()
436
437 class SliceRoleAdmin(PlanetStackBaseAdmin):
438     model = SliceRole
439     pass
440
441 class SiteRoleAdmin(PlanetStackBaseAdmin):
442     model = SiteRole
443     pass
444
445 class DeploymentAdminForm(forms.ModelForm):
446     sites = forms.ModelMultipleChoiceField(
447         queryset=Site.objects.all(),
448         required=False,
449         widget=FilteredSelectMultiple(
450             verbose_name=('Sites'), is_stacked=False
451         )
452     )
453     class Meta:
454         model = Deployment
455
456     def __init__(self, *args, **kwargs):
457       super(DeploymentAdminForm, self).__init__(*args, **kwargs)
458
459       if self.instance and self.instance.pk:
460         self.fields['sites'].initial = self.instance.sites.all()
461
462     def save(self, commit=True):
463       deployment = super(DeploymentAdminForm, self).save(commit=False)
464
465       if commit:
466         deployment.save()
467
468       if deployment.pk:
469         deployment.sites = self.cleaned_data['sites']
470         self.save_m2m()
471
472       return deployment
473
474 class SiteAssocInline(PlStackTabularInline):
475     model = Site.deployments.through
476     extra = 0
477     suit_classes = 'suit-tab suit-tab-sites'
478
479 class DeploymentAdmin(PlanetStackBaseAdmin):
480     form = DeploymentAdminForm
481     model = Deployment
482     fieldList = ['name','sites']
483     fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
484     inlines = [DeploymentPrivilegeInline,NodeInline,TagInline]
485
486     user_readonly_inlines = [DeploymentPrivilegeROInline,NodeROInline,TagROInline]
487     user_readonly_fields = ['name']
488
489     suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags'))
490
491 class ServiceAttrAsTabROInline(ReadOnlyTabularInline):
492     model = ServiceAttribute
493     fields = ['name','value']
494     extra = 0
495     suit_classes = 'suit-tab suit-tab-serviceattrs'
496
497 class ServiceAttrAsTabInline(PlStackTabularInline):
498     model = ServiceAttribute
499     fields = ['name','value']
500     extra = 0
501     suit_classes = 'suit-tab suit-tab-serviceattrs'
502
503 class ServiceAdmin(PlanetStackBaseAdmin):
504     list_display = ("name","description","versionNumber","enabled","published")
505     fieldList = ["name","description","versionNumber","enabled","published"]
506     fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
507     inlines = [ServiceAttrAsTabInline,SliceInline]
508
509     user_readonly_fields = fieldList
510     user_readonly_inlines = [ServiceAttrAsTabROInline,SliceROInline]
511
512     suit_form_tabs =(('general', 'Service Details'),
513         ('slices','Slices'),
514         ('serviceattrs','Additional Attributes'),
515     )
516
517 class SiteAdmin(PlanetStackBaseAdmin):
518     fieldList = ['name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
519     fieldsets = [
520         (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
521         #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
522     ]
523     suit_form_tabs =(('general', 'Site Details'),
524         ('users','Users'),
525         ('siteprivileges','Privileges'),
526         ('deployments','Deployments'),
527         ('slices','Slices'),
528         ('nodes','Nodes'), 
529         ('tags','Tags'),
530     )
531     readonly_fields = ['accountLink']
532
533     user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
534     user_readonly_inlines = [SliceROInline,UserROInline,TagROInline, NodeROInline, SitePrivilegeROInline,SiteDeploymentROInline]
535
536     list_display = ('name', 'login_base','site_url', 'enabled')
537     filter_horizontal = ('deployments',)
538     inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentInline]
539     search_fields = ['name']
540
541     def queryset(self, request):
542         #print dir(UserInline)
543         return Site.select_by_user(request.user)
544
545     def get_formsets(self, request, obj=None):
546         for inline in self.get_inline_instances(request, obj):
547             # hide MyInline in the add view
548             if obj is None:
549                 continue
550             if isinstance(inline, SliceInline):
551                 inline.model.caller = request.user
552             yield inline.get_formset(request, obj)
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, SliverInline):
560                 inline.model.caller = request.user
561             yield inline.get_formset(request, obj)
562
563     def accountLink(self, obj):
564         link_obj = obj.accounts.all()
565         if link_obj:
566             reverse_path = "admin:core_account_change"
567             url = reverse(reverse_path, args =(link_obj[0].id,))
568             return "<a href='%s'>%s</a>" % (url, "view billing details")
569         else:
570             return "no billing data for this site"
571     accountLink.allow_tags = True
572     accountLink.short_description = "Billing"
573
574     def save_model(self, request, obj, form, change):
575         # update openstack connection to use this site/tenant
576         obj.save_by_user(request.user) 
577
578     def delete_model(self, request, obj):
579         obj.delete_by_user(request.user)
580         
581
582 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
583     fieldList = ['user', 'site', 'role']
584     fieldsets = [
585         (None, {'fields': fieldList, 'classes':['collapse']})
586     ]
587     list_display = ('user', 'site', 'role')
588     user_readonly_fields = fieldList
589     user_readonly_inlines = []
590
591     def formfield_for_foreignkey(self, db_field, request, **kwargs):
592         if db_field.name == 'site':
593             if not request.user.is_admin:
594                 # only show sites where user is an admin or pi
595                 sites = set()
596                 for site_privilege in SitePrivilege.objects.filer(user=request.user):
597                     if site_privilege.role.role_type in ['admin', 'pi']:
598                         sites.add(site_privilege.site)
599                 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
600
601         if db_field.name == 'user':
602             if not request.user.is_admin:
603                 # only show users from sites where caller has admin or pi role
604                 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
605                 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
606                 sites = [site_privilege.site for site_privilege in site_privileges]
607                 site_privileges = SitePrivilege.objects.filter(site__in=sites)
608                 emails = [site_privilege.user.email for site_privilege in site_privileges]
609                 users = User.objects.filter(email__in=emails)
610                 kwargs['queryset'] = users
611
612         return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
613
614     def queryset(self, request):
615         # admins can see all privileges. Users can only see privileges at sites
616         # where they have the admin role or pi role.
617         qs = super(SitePrivilegeAdmin, self).queryset(request)
618         #if not request.user.is_admin:
619         #    roles = Role.objects.filter(role_type__in=['admin', 'pi'])
620         #    site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
621         #    login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
622         #    sites = Site.objects.filter(login_base__in=login_bases)
623         #    qs = qs.filter(site__in=sites)
624         return qs
625
626 class SliceForm(forms.ModelForm):
627     class Meta:
628         model = Slice
629         widgets = {
630             'service': LinkedSelect 
631         }
632
633 class SliceAdmin(PlanetStackBaseAdmin):
634     form = SliceForm
635     fieldList = ['name', 'site', 'serviceClass', 'enabled','description', 'service', 'slice_url']
636     fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
637     list_display = ('name', 'site','serviceClass', 'slice_url')
638     inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
639
640     user_readonly_fields = fieldList
641     user_readonly_inlines = [SlicePrivilegeROInline,SliverROInline,TagROInline, ReservationROInline, SliceNetworkROInline]
642
643     suit_form_tabs =(('general', 'Slice Details'),
644         ('slicenetworks','Networks'),
645         ('sliceprivileges','Privileges'),
646         ('slivers','Slivers'),
647         ('tags','Tags'),
648         ('reservations','Reservations'),
649     )
650
651     def formfield_for_foreignkey(self, db_field, request, **kwargs):
652         if db_field.name == 'site':
653             kwargs['queryset'] = Site.select_by_user(request.user)
654                 
655         return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
656
657     def queryset(self, request):
658         # admins can see all keys. Users can only see slices they belong to.
659         return Slice.select_by_user(request.user)
660
661     def get_formsets(self, request, obj=None):
662         for inline in self.get_inline_instances(request, obj):
663             # hide MyInline in the add view
664             if obj is None:
665                 continue
666             if isinstance(inline, SliverInline):
667                 inline.model.caller = request.user
668             yield inline.get_formset(request, obj)
669
670
671 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
672     fieldsets = [
673         (None, {'fields': ['user', 'slice', 'role']})
674     ]
675     list_display = ('user', 'slice', 'role')
676
677     user_readonly_fields = ['user', 'slice', 'role']
678     user_readonly_inlines = []
679
680     def formfield_for_foreignkey(self, db_field, request, **kwargs):
681         if db_field.name == 'slice':
682             kwargs['queryset'] = Slice.select_by_user(request.user)
683         
684         if db_field.name == 'user':
685             kwargs['queryset'] = User.select_by_user(request.user)
686
687         return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
688
689     def queryset(self, request):
690         # admins can see all memberships. Users can only see memberships of
691         # slices where they have the admin role.
692         return SlicePrivilege.select_by_user(request.user)
693
694     def save_model(self, request, obj, form, change):
695         # update openstack connection to use this site/tenant
696         auth = request.session.get('auth', {})
697         auth['tenant'] = obj.slice.name
698         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
699         obj.save()
700
701     def delete_model(self, request, obj):
702         # update openstack connection to use this site/tenant
703         auth = request.session.get('auth', {})
704         auth['tenant'] = obj.slice.name
705         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
706         obj.delete()
707
708
709 class ImageAdmin(PlanetStackBaseAdmin):
710
711     fieldsets = [('Image Details', 
712                    {'fields': ['image_id', 'name', 'disk_format', 'container_format'], 
713                     'classes': ['suit-tab suit-tab-general']})
714                ]
715
716     suit_form_tabs =(('general','Image Details'),('slivers','Slivers'))
717
718     inlines = [SliverInline]
719     
720     user_readonly_fields = ['image_id', 'name', 'disk_format', 'container_format']
721     user_readonly_inlines = [SliverROInline]
722     
723 class NodeForm(forms.ModelForm):
724     class Meta:
725         widgets = {
726             'site': LinkedSelect,
727             'deployment': LinkedSelect
728         }
729
730 class NodeAdmin(PlanetStackBaseAdmin):
731     form = NodeForm
732     list_display = ('name', 'site', 'deployment')
733     list_filter = ('deployment',)
734
735     inlines = [TagInline,SliverInline]
736     fieldsets = [('Node Details', {'fields': ['name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
737
738     user_readonly_fields = ['name','site','deployment']
739     user_readonly_inlines = [TagInline,SliverInline]
740
741     suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
742
743
744 class SliverForm(forms.ModelForm):
745     class Meta:
746         model = Sliver
747         ip = forms.CharField(widget=PlainTextWidget)
748         instance_name = forms.CharField(widget=PlainTextWidget)
749         widgets = {
750             'ip': PlainTextWidget(),
751             'instance_name': PlainTextWidget(),
752             'slice': LinkedSelect,
753             'deploymentNetwork': LinkedSelect,
754             'node': LinkedSelect,
755             'image': LinkedSelect
756         }
757
758 class TagAdmin(PlanetStackBaseAdmin):
759     list_display = ['service', 'name', 'value', 'content_type', 'content_object',]
760     user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
761     user_readonly_inlines = []
762
763 class SliverAdmin(PlanetStackBaseAdmin):
764     form = SliverForm
765     fieldsets = [
766         ('Sliver Details', {'fields': ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
767     ]
768     list_display = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']
769
770     suit_form_tabs =(('general', 'Sliver Details'),
771         ('tags','Tags'),
772     )
773
774     inlines = [TagInline]
775
776     user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image']
777     user_readonly_inlines = [TagROInline]
778
779     def formfield_for_foreignkey(self, db_field, request, **kwargs):
780         if db_field.name == 'slice':
781             kwargs['queryset'] = Slice.select_by_user(request.user)
782
783         return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
784
785     def queryset(self, request):
786         # admins can see all slivers. Users can only see slivers of 
787         # the slices they belong to.
788         return Sliver.select_by_user(request.user)
789
790
791     def get_formsets(self, request, obj=None):
792         # make some fields read only if we are updating an existing record
793         if obj == None:
794             #self.readonly_fields = ('ip', 'instance_name') 
795             self.readonly_fields = () 
796         else:
797             self.readonly_fields = () 
798             #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key') 
799
800         for inline in self.get_inline_instances(request, obj):
801             # hide MyInline in the add view
802             if obj is None:
803                 continue
804
805     #def save_model(self, request, obj, form, change):
806     #    # update openstack connection to use this site/tenant
807     #    auth = request.session.get('auth', {})
808     #    auth['tenant'] = obj.slice.name
809     #    obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
810     #    obj.creator = request.user
811     #    obj.save()
812
813     #def delete_model(self, request, obj):
814     #    # update openstack connection to use this site/tenant
815     #    auth = request.session.get('auth', {})
816     #    auth['tenant'] = obj.slice.name
817     #    obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
818     #    obj.delete()
819
820 class UserCreationForm(forms.ModelForm):
821     """A form for creating new users. Includes all the required
822     fields, plus a repeated password."""
823     password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
824     password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
825
826     class Meta:
827         model = User
828         fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
829
830     def clean_password2(self):
831         # Check that the two password entries match
832         password1 = self.cleaned_data.get("password1")
833         password2 = self.cleaned_data.get("password2")
834         if password1 and password2 and password1 != password2:
835             raise forms.ValidationError("Passwords don't match")
836         return password2
837
838     def save(self, commit=True):
839         # Save the provided password in hashed format
840         user = super(UserCreationForm, self).save(commit=False)
841         user.password = self.cleaned_data["password1"]
842         #user.set_password(self.cleaned_data["password1"])
843         if commit:
844             user.save()
845         return user
846
847
848 class UserChangeForm(forms.ModelForm):
849     """A form for updating users. Includes all the fields on
850     the user, but replaces the password field with admin's
851     password hash display field.
852     """
853     password = ReadOnlyPasswordHashField(label='Password',
854                    help_text= '<a href=\"password/\">Change Password</a>.')
855
856     class Meta:
857         model = User
858
859     def clean_password(self):
860         # Regardless of what the user provides, return the initial value.
861         # This is done here, rather than on the field, because the
862         # field does not have access to the initial value
863         return self.initial["password"]
864
865 class UserAdmin(UserAdmin):
866     class Meta:
867         app_label = "core"
868
869     # The forms to add and change user instances
870     form = UserChangeForm
871     add_form = UserCreationForm
872
873     # The fields to be used in displaying the User model.
874     # These override the definitions on the base UserAdmin
875     # that reference specific fields on auth.User.
876     list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
877     #list_display = ('email', 'username','firstname', 'lastname', 'is_admin', 'last_login')
878     list_filter = ('site',)
879     inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline]
880
881     fieldListLoginDetails = ['email','site','password','is_readonly','is_amin','public_key']
882     fieldListContactInfo = ['firstname','lastname','phone','timezone']
883
884     fieldsets = (
885         ('Login Details', {'fields': ['email', 'site','password', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
886         ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
887         #('Important dates', {'fields': ('last_login',)}),
888     )
889     add_fieldsets = (
890         (None, {
891             'classes': ('wide',),
892             'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
893         ),
894     )
895     search_fields = ('email',)
896     ordering = ('email',)
897     filter_horizontal = ()
898
899     user_readonly_fields = fieldListLoginDetails
900     user_readonly_inlines = [SlicePrivilegeROInline,SitePrivilegeROInline,DeploymentPrivilegeROInline]
901
902     suit_form_tabs =(('general','Login Details'),('contact','Contact Information'),('sliceprivileges','Slice Privileges'),('siteprivileges','Site Privileges'),('deploymentprivileges','Deployment Privileges'))
903
904     def formfield_for_foreignkey(self, db_field, request, **kwargs):
905         if db_field.name == 'site':
906             kwargs['queryset'] = Site.select_by_user(request.user)
907
908         return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
909
910     def has_add_permission(self, request, obj=None):
911         return (not self.__user_is_readonly(request))
912
913     def has_delete_permission(self, request, obj=None):
914         return (not self.__user_is_readonly(request))
915
916     def get_actions(self,request):
917         actions = super(UserAdmin,self).get_actions(request)
918
919         if self.__user_is_readonly(request):
920             if 'delete_selected' in actions:
921                 del actions['delete_selected']
922
923         return actions
924
925     def change_view(self,request,object_id, extra_context=None):
926
927         if self.__user_is_readonly(request):
928             self.readonly_fields=self.user_readonly_fields
929             self.inlines = self.user_readonly_inlines
930         try:
931             return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
932         except PermissionDenied:
933             pass
934         if request.method == 'POST':
935             raise PermissionDenied
936         request.readonly = True
937         return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
938
939     def __user_is_readonly(self, request):
940         #groups = [x.name for x in request.user.groups.all() ]
941         #return "readonly" in groups
942         return request.user.isReadOnlyUser()
943
944     def queryset(self, request):
945         return User.select_by_user(request.user)
946
947
948
949 class ServiceResourceROInline(ReadOnlyTabularInline):
950     model = ServiceResource
951     extra = 0
952     fields = ['serviceClass', 'name', 'maxUnitsDeployment', 'maxUnitsNode', 'maxDuration', 'bucketInRate', 'bucketMaxSize', 'cost', 'calendarReservable']
953
954 class ServiceResourceInline(PlStackTabularInline):
955     model = ServiceResource
956     extra = 0
957
958 class ServiceClassAdmin(PlanetStackBaseAdmin):
959     list_display = ('name', 'commitment', 'membershipFee')
960     inlines = [ServiceResourceInline]
961
962     user_readonly_fields = ['name', 'commitment', 'membershipFee']
963     user_readonly_inlines = []
964
965 class ReservedResourceROInline(ReadOnlyTabularInline):
966     model = ReservedResource
967     extra = 0
968     fields = ['sliver', 'resource','quantity','reservationSet']
969     suit_classes = 'suit-tab suit-tab-reservedresources'
970
971 class ReservedResourceInline(PlStackTabularInline):
972     model = ReservedResource
973     extra = 0
974     suit_classes = 'suit-tab suit-tab-reservedresources'
975
976     def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
977         field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
978
979         if db_field.name == 'resource':
980             # restrict resources to those that the slice's service class allows
981             if request._slice is not None:
982                 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
983                 if len(field.queryset) > 0:
984                     field.initial = field.queryset.all()[0]
985             else:\r
986                 field.queryset = field.queryset.none()\r
987         elif db_field.name == 'sliver':\r
988             # restrict slivers to those that belong to the slice\r
989             if request._slice is not None:\r
990                 field.queryset = field.queryset.filter(slice = request._slice)
991             else:
992                 field.queryset = field.queryset.none()\r
993 \r
994         return field
995
996     def queryset(self, request):
997         return ReservedResource.select_by_user(request.user)
998
999 class ReservationChangeForm(forms.ModelForm):
1000     class Meta:
1001         model = Reservation
1002         widgets = {
1003             'slice' : LinkedSelect
1004         }
1005
1006 class ReservationAddForm(forms.ModelForm):
1007     slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1008     refresh = forms.CharField(widget=forms.HiddenInput())
1009
1010     class Media:
1011        css = {'all': ('planetstack.css',)}   # .field-refresh { display: none; }
1012
1013     def clean_slice(self):
1014         slice = self.cleaned_data.get("slice")
1015         x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1016         if len(x) == 0:
1017             raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1018         return slice
1019
1020     class Meta:
1021         model = Reservation
1022         widgets = {
1023             'slice' : LinkedSelect
1024         }
1025
1026
1027 class ReservationAddRefreshForm(ReservationAddForm):
1028     """ This form is displayed when the Reservation Form receives an update
1029         from the Slice dropdown onChange handler. It doesn't validate the
1030         data and doesn't save the data. This will cause the form to be
1031         redrawn.
1032     """
1033
1034     """ don't validate anything other than slice """
1035     dont_validate_fields = ("startTime", "duration")
1036
1037     def full_clean(self):
1038         result = super(ReservationAddForm, self).full_clean()
1039
1040         for fieldname in self.dont_validate_fields:
1041             if fieldname in self._errors:
1042                 del self._errors[fieldname]
1043
1044         return result
1045
1046     """ don't save anything """
1047     def is_valid(self):
1048         return False
1049
1050 class ReservationAdmin(PlanetStackBaseAdmin):
1051     fieldList = ['slice', 'startTime', 'duration']
1052     fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1053     list_display = ('startTime', 'duration')
1054     form = ReservationAddForm
1055
1056     suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1057
1058     inlines = [ReservedResourceInline]
1059     user_readonly_inlines = [ReservedResourceROInline]
1060     user_readonly_fields = fieldList
1061
1062     def add_view(self, request, form_url='', extra_context=None):
1063         timezone.activate(request.user.timezone)
1064         request._refresh = False
1065         request._slice = None
1066         if request.method == 'POST':
1067             # "refresh" will be set to "1" if the form was submitted due to
1068             # a change in the Slice dropdown.
1069             if request.POST.get("refresh","1") == "1":
1070                 request._refresh = True
1071                 request.POST["refresh"] = "0"
1072
1073             # Keep track of the slice that was selected, so the
1074             # reservedResource inline can filter items for the slice.
1075             request._slice = request.POST.get("slice",None)
1076             if (request._slice is not None):
1077                 request._slice = Slice.objects.get(id=request._slice)
1078
1079         result =  super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1080         return result
1081
1082     def changelist_view(self, request, extra_context = None):
1083         timezone.activate(request.user.timezone)
1084         return super(ReservationAdmin, self).changelist_view(request, extra_context)
1085
1086     def get_form(self, request, obj=None, **kwargs):
1087         request._obj_ = obj
1088         if obj is not None:
1089             # For changes, set request._slice to the slice already set in the
1090             # object.
1091             request._slice = obj.slice
1092             self.form = ReservationChangeForm
1093         else:
1094             if getattr(request, "_refresh", False):
1095                 self.form = ReservationAddRefreshForm
1096             else:
1097                 self.form = ReservationAddForm
1098         return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1099
1100     def get_readonly_fields(self, request, obj=None):
1101         if (obj is not None):
1102             # Prevent slice from being changed after the reservation has been
1103             # created.
1104             return ['slice']
1105         else:
1106             return []
1107
1108     def queryset(self, request):
1109         return Reservation.select_by_user(request.user)
1110
1111 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1112     list_display = ("name", )
1113     user_readonly_fields = ['name']
1114     user_readonly_inlines = []
1115
1116 class RouterAdmin(PlanetStackBaseAdmin):
1117     list_display = ("name", )
1118     user_readonly_fields = ['name']
1119     user_readonly_inlines = []
1120
1121 class RouterROInline(ReadOnlyTabularInline):
1122     model = Router.networks.through
1123     extra = 0
1124     verbose_name_plural = "Routers"
1125     verbose_name = "Router"
1126     suit_classes = 'suit-tab suit-tab-routers'
1127
1128     fields = ['name', 'owner', 'permittedNetworks', 'networks']
1129
1130 class RouterInline(PlStackTabularInline):
1131     model = Router.networks.through
1132     extra = 0
1133     verbose_name_plural = "Routers"
1134     verbose_name = "Router"
1135     suit_classes = 'suit-tab suit-tab-routers'
1136
1137 class NetworkParameterROInline(ReadOnlyTabularInline):
1138     model = NetworkParameter
1139     extra = 1
1140     verbose_name_plural = "Parameters"
1141     verbose_name = "Parameter"
1142     suit_classes = 'suit-tab suit-tab-netparams'
1143     fields = ['parameter', 'value', 'content_type', 'object_id', 'content_object']
1144
1145 class NetworkParameterInline(generic.GenericTabularInline):
1146     model = NetworkParameter
1147     extra = 1
1148     verbose_name_plural = "Parameters"
1149     verbose_name = "Parameter"
1150     suit_classes = 'suit-tab suit-tab-netparams'
1151
1152 class NetworkSliversROInline(ReadOnlyTabularInline):
1153     fields = ['network', 'sliver', 'ip', 'port_id']
1154     model = NetworkSliver
1155     extra = 0
1156     verbose_name_plural = "Slivers"
1157     verbose_name = "Sliver"
1158     suit_classes = 'suit-tab suit-tab-networkslivers'
1159
1160 class NetworkSliversInline(PlStackTabularInline):
1161     readonly_fields = ("ip", )
1162     model = NetworkSliver
1163     selflink_fieldname = "sliver"
1164     extra = 0
1165     verbose_name_plural = "Slivers"
1166     verbose_name = "Sliver"
1167     suit_classes = 'suit-tab suit-tab-networkslivers'
1168
1169 class NetworkSlicesROInline(ReadOnlyTabularInline):
1170     model = NetworkSlice
1171     extra = 0
1172     verbose_name_plural = "Slices"
1173     verbose_name = "Slice"
1174     suit_classes = 'suit-tab suit-tab-networkslices'
1175     fields = ['network','slice']
1176
1177 class NetworkSlicesInline(PlStackTabularInline):
1178     model = NetworkSlice
1179     selflink_fieldname = "slice"
1180     extra = 0
1181     verbose_name_plural = "Slices"
1182     verbose_name = "Slice"
1183     suit_classes = 'suit-tab suit-tab-networkslices'
1184
1185 class NetworkAdmin(PlanetStackBaseAdmin):
1186     list_display = ("name", "subnet", "ports", "labels")
1187     readonly_fields = ("subnet", )
1188
1189     inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1190
1191     fieldsets = [
1192         (None, {'fields': ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet'], 'classes':['suit-tab suit-tab-general']}),]
1193
1194     user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1195     user_readonly_inlines = [NetworkParameterROInline, NetworkSliversROInline, NetworkSlicesROInline, RouterROInline]
1196
1197     suit_form_tabs =(
1198         ('general','Network Details'),
1199         ('netparams', 'Parameters'),
1200         ('networkslivers','Slivers'),
1201         ('networkslices','Slices'),
1202         ('routers','Routers'),
1203     )
1204 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1205     list_display = ("name", "guaranteedBandwidth", "visibility")
1206     user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1207     user_readonly_inlines = []
1208
1209 # register a signal that caches the user's credentials when they log in
1210 def cache_credentials(sender, user, request, **kwds):
1211     auth = {'username': request.POST['username'],
1212             'password': request.POST['password']}
1213     request.session['auth'] = auth
1214 user_logged_in.connect(cache_credentials)
1215
1216 def dollar_field(fieldName, short_description):
1217     def newFunc(self, obj):
1218         try:
1219             x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1220         except:
1221             x=getattr(obj, fieldName, 0.0)
1222         return x
1223     newFunc.short_description = short_description
1224     return newFunc
1225
1226 def right_dollar_field(fieldName, short_description):
1227     def newFunc(self, obj):
1228         try:
1229             #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1230             x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1231         except:
1232             x=getattr(obj, fieldName, 0.0)
1233         return x
1234     newFunc.short_description = short_description
1235     newFunc.allow_tags = True
1236     return newFunc
1237
1238 class InvoiceChargeInline(PlStackTabularInline):
1239     model = Charge
1240     extra = 0
1241     verbose_name_plural = "Charges"
1242     verbose_name = "Charge"
1243     exclude = ['account']
1244     fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1245     readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1246     can_delete = False
1247     max_num = 0
1248
1249     dollar_amount = right_dollar_field("amount", "Amount")
1250
1251 class InvoiceAdmin(admin.ModelAdmin):
1252     list_display = ("date", "account")
1253
1254     inlines = [InvoiceChargeInline]
1255
1256     fields = ["date", "account", "dollar_amount"]
1257     readonly_fields = ["date", "account", "dollar_amount"]
1258
1259     dollar_amount = dollar_field("amount", "Amount")
1260
1261 class InvoiceInline(PlStackTabularInline):
1262     model = Invoice
1263     extra = 0
1264     verbose_name_plural = "Invoices"
1265     verbose_name = "Invoice"
1266     fields = ["date", "dollar_amount"]
1267     readonly_fields = ["date", "dollar_amount"]
1268     suit_classes = 'suit-tab suit-tab-accountinvoice'
1269     can_delete=False
1270     max_num=0
1271
1272     dollar_amount = right_dollar_field("amount", "Amount")
1273
1274 class PendingChargeInline(PlStackTabularInline):
1275     model = Charge
1276     extra = 0
1277     verbose_name_plural = "Charges"
1278     verbose_name = "Charge"
1279     exclude = ["invoice"]
1280     fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1281     readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1282     suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1283     can_delete=False
1284     max_num=0
1285
1286     def queryset(self, request):
1287         qs = super(PendingChargeInline, self).queryset(request)
1288         qs = qs.filter(state="pending")
1289         return qs
1290
1291     dollar_amount = right_dollar_field("amount", "Amount")
1292
1293 class PaymentInline(PlStackTabularInline):
1294     model=Payment
1295     extra = 1
1296     verbose_name_plural = "Payments"
1297     verbose_name = "Payment"
1298     fields = ["date", "dollar_amount"]
1299     readonly_fields = ["date", "dollar_amount"]
1300     suit_classes = 'suit-tab suit-tab-accountpayments'
1301     can_delete=False
1302     max_num=0
1303
1304     dollar_amount = right_dollar_field("amount", "Amount")
1305
1306 class AccountAdmin(admin.ModelAdmin):
1307     list_display = ("site", "balance_due")
1308
1309     inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1310
1311     fieldsets = [
1312         (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1313
1314     readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1315
1316     suit_form_tabs =(
1317         ('general','Account Details'),
1318         ('accountinvoice', 'Invoices'),
1319         ('accountpayments', 'Payments'),
1320         ('accountpendingcharges','Pending Charges'),
1321     )
1322
1323     dollar_balance_due = dollar_field("balance_due", "Balance Due")
1324     dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1325     dollar_total_payments = dollar_field("total_payments", "Total Payments")
1326
1327
1328 # Now register the new UserAdmin...
1329 admin.site.register(User, UserAdmin)
1330 # ... and, since we're not using Django's builtin permissions,
1331 # unregister the Group model from admin.
1332 #admin.site.unregister(Group)
1333
1334 #Do not show django evolution in the admin interface
1335 from django_evolution.models import Version, Evolution
1336 #admin.site.unregister(Version)
1337 #admin.site.unregister(Evolution)
1338
1339
1340 # When debugging it is often easier to see all the classes, but for regular use 
1341 # only the top-levels should be displayed
1342 showAll = False
1343
1344 admin.site.register(Deployment, DeploymentAdmin)
1345 admin.site.register(Site, SiteAdmin)
1346 admin.site.register(Slice, SliceAdmin)
1347 admin.site.register(Service, ServiceAdmin)
1348 admin.site.register(Reservation, ReservationAdmin)
1349 admin.site.register(Network, NetworkAdmin)
1350 admin.site.register(Router, RouterAdmin)
1351 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1352 admin.site.register(Account, AccountAdmin)
1353 admin.site.register(Invoice, InvoiceAdmin)
1354
1355 if True:
1356     admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1357     admin.site.register(ServiceClass, ServiceClassAdmin)
1358     #admin.site.register(PlanetStack)
1359     admin.site.register(Tag, TagAdmin)
1360     admin.site.register(DeploymentRole)
1361     admin.site.register(SiteRole)
1362     admin.site.register(SliceRole)
1363     admin.site.register(PlanetStackRole)
1364     admin.site.register(Node, NodeAdmin)
1365     #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1366     #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1367     admin.site.register(Sliver, SliverAdmin)
1368     admin.site.register(Image, ImageAdmin)
1369