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