6db4ef8527f031cccdeec1264aa95e1002af759d
[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 UserDashboardViewROInline(ReadOnlyTabularInline):
884     model = UserDashboardView
885     extra = 0
886     suit_classes = 'suit-tab suit-tab-dashboards'
887     fields = ['user', 'dashboardView', 'order']
888
889 class UserAdmin(UserAdmin):
890     class Meta:
891         app_label = "core"
892
893     # The forms to add and change user instances
894     form = UserChangeForm
895     add_form = UserCreationForm
896
897     # The fields to be used in displaying the User model.
898     # These override the definitions on the base UserAdmin
899     # that reference specific fields on auth.User.
900     list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
901     #list_display = ('email', 'username','firstname', 'lastname', 'is_admin', 'last_login')
902     list_filter = ('site',)
903     inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
904
905     fieldListLoginDetails = ['email','site','password','is_readonly','is_amin','public_key']
906     fieldListContactInfo = ['firstname','lastname','phone','timezone']
907
908     fieldsets = (
909         ('Login Details', {'fields': ['email', 'site','password', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
910         ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
911         #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
912         #('Important dates', {'fields': ('last_login',)}),
913     )
914     add_fieldsets = (
915         (None, {
916             'classes': ('wide',),
917             'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
918         ),
919     )
920     search_fields = ('email',)
921     ordering = ('email',)
922     filter_horizontal = ()
923
924     user_readonly_fields = fieldListLoginDetails
925     user_readonly_inlines = [SlicePrivilegeROInline,SitePrivilegeROInline,DeploymentPrivilegeROInline,UserDashboardViewROInline]
926
927     suit_form_tabs =(('general','Login Details'),
928                      ('contact','Contact Information'),
929                      ('sliceprivileges','Slice Privileges'),
930                      ('siteprivileges','Site Privileges'),
931                      ('deploymentprivileges','Deployment Privileges'),
932                      ('dashboards','Dashboard Views'))
933
934     def formfield_for_foreignkey(self, db_field, request, **kwargs):
935         if db_field.name == 'site':
936             kwargs['queryset'] = Site.select_by_user(request.user)
937
938         return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
939
940     def has_add_permission(self, request, obj=None):
941         return (not self.__user_is_readonly(request))
942
943     def has_delete_permission(self, request, obj=None):
944         return (not self.__user_is_readonly(request))
945
946     def get_actions(self,request):
947         actions = super(UserAdmin,self).get_actions(request)
948
949         if self.__user_is_readonly(request):
950             if 'delete_selected' in actions:
951                 del actions['delete_selected']
952
953         return actions
954
955     def change_view(self,request,object_id, extra_context=None):
956
957         if self.__user_is_readonly(request):
958             self.readonly_fields=self.user_readonly_fields
959             self.inlines = self.user_readonly_inlines
960         try:
961             return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
962         except PermissionDenied:
963             pass
964         if request.method == 'POST':
965             raise PermissionDenied
966         request.readonly = True
967         return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
968
969     def __user_is_readonly(self, request):
970         #groups = [x.name for x in request.user.groups.all() ]
971         #return "readonly" in groups
972         return request.user.isReadOnlyUser()
973
974     def queryset(self, request):
975         return User.select_by_user(request.user)
976
977 class DashboardViewAdmin(PlanetStackBaseAdmin):
978     fieldsets = [('Dashboard View Details',
979                    {'fields': ['name', 'url'],
980                     'classes': ['suit-tab suit-tab-general']})
981                ]
982
983     suit_form_tabs =(('general','Dashboard View Details'),)
984
985 class ServiceResourceROInline(ReadOnlyTabularInline):
986     model = ServiceResource
987     extra = 0
988     fields = ['serviceClass', 'name', 'maxUnitsDeployment', 'maxUnitsNode', 'maxDuration', 'bucketInRate', 'bucketMaxSize', 'cost', 'calendarReservable']
989
990 class ServiceResourceInline(PlStackTabularInline):
991     model = ServiceResource
992     extra = 0
993
994 class ServiceClassAdmin(PlanetStackBaseAdmin):
995     list_display = ('name', 'commitment', 'membershipFee')
996     inlines = [ServiceResourceInline]
997
998     user_readonly_fields = ['name', 'commitment', 'membershipFee']
999     user_readonly_inlines = []
1000
1001 class ReservedResourceROInline(ReadOnlyTabularInline):
1002     model = ReservedResource
1003     extra = 0
1004     fields = ['sliver', 'resource','quantity','reservationSet']
1005     suit_classes = 'suit-tab suit-tab-reservedresources'
1006
1007 class ReservedResourceInline(PlStackTabularInline):
1008     model = ReservedResource
1009     extra = 0
1010     suit_classes = 'suit-tab suit-tab-reservedresources'
1011
1012     def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1013         field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1014
1015         if db_field.name == 'resource':
1016             # restrict resources to those that the slice's service class allows
1017             if request._slice is not None:
1018                 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1019                 if len(field.queryset) > 0:
1020                     field.initial = field.queryset.all()[0]
1021             else:\r
1022                 field.queryset = field.queryset.none()\r
1023         elif db_field.name == 'sliver':\r
1024             # restrict slivers to those that belong to the slice\r
1025             if request._slice is not None:\r
1026                 field.queryset = field.queryset.filter(slice = request._slice)
1027             else:
1028                 field.queryset = field.queryset.none()\r
1029 \r
1030         return field
1031
1032     def queryset(self, request):
1033         return ReservedResource.select_by_user(request.user)
1034
1035 class ReservationChangeForm(forms.ModelForm):
1036     class Meta:
1037         model = Reservation
1038         widgets = {
1039             'slice' : LinkedSelect
1040         }
1041
1042 class ReservationAddForm(forms.ModelForm):
1043     slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1044     refresh = forms.CharField(widget=forms.HiddenInput())
1045
1046     class Media:
1047        css = {'all': ('planetstack.css',)}   # .field-refresh { display: none; }
1048
1049     def clean_slice(self):
1050         slice = self.cleaned_data.get("slice")
1051         x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1052         if len(x) == 0:
1053             raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1054         return slice
1055
1056     class Meta:
1057         model = Reservation
1058         widgets = {
1059             'slice' : LinkedSelect
1060         }
1061
1062
1063 class ReservationAddRefreshForm(ReservationAddForm):
1064     """ This form is displayed when the Reservation Form receives an update
1065         from the Slice dropdown onChange handler. It doesn't validate the
1066         data and doesn't save the data. This will cause the form to be
1067         redrawn.
1068     """
1069
1070     """ don't validate anything other than slice """
1071     dont_validate_fields = ("startTime", "duration")
1072
1073     def full_clean(self):
1074         result = super(ReservationAddForm, self).full_clean()
1075
1076         for fieldname in self.dont_validate_fields:
1077             if fieldname in self._errors:
1078                 del self._errors[fieldname]
1079
1080         return result
1081
1082     """ don't save anything """
1083     def is_valid(self):
1084         return False
1085
1086 class ReservationAdmin(PlanetStackBaseAdmin):
1087     fieldList = ['slice', 'startTime', 'duration']
1088     fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1089     list_display = ('startTime', 'duration')
1090     form = ReservationAddForm
1091
1092     suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1093
1094     inlines = [ReservedResourceInline]
1095     user_readonly_inlines = [ReservedResourceROInline]
1096     user_readonly_fields = fieldList
1097
1098     def add_view(self, request, form_url='', extra_context=None):
1099         timezone.activate(request.user.timezone)
1100         request._refresh = False
1101         request._slice = None
1102         if request.method == 'POST':
1103             # "refresh" will be set to "1" if the form was submitted due to
1104             # a change in the Slice dropdown.
1105             if request.POST.get("refresh","1") == "1":
1106                 request._refresh = True
1107                 request.POST["refresh"] = "0"
1108
1109             # Keep track of the slice that was selected, so the
1110             # reservedResource inline can filter items for the slice.
1111             request._slice = request.POST.get("slice",None)
1112             if (request._slice is not None):
1113                 request._slice = Slice.objects.get(id=request._slice)
1114
1115         result =  super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1116         return result
1117
1118     def changelist_view(self, request, extra_context = None):
1119         timezone.activate(request.user.timezone)
1120         return super(ReservationAdmin, self).changelist_view(request, extra_context)
1121
1122     def get_form(self, request, obj=None, **kwargs):
1123         request._obj_ = obj
1124         if obj is not None:
1125             # For changes, set request._slice to the slice already set in the
1126             # object.
1127             request._slice = obj.slice
1128             self.form = ReservationChangeForm
1129         else:
1130             if getattr(request, "_refresh", False):
1131                 self.form = ReservationAddRefreshForm
1132             else:
1133                 self.form = ReservationAddForm
1134         return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1135
1136     def get_readonly_fields(self, request, obj=None):
1137         if (obj is not None):
1138             # Prevent slice from being changed after the reservation has been
1139             # created.
1140             return ['slice']
1141         else:
1142             return []
1143
1144     def queryset(self, request):
1145         return Reservation.select_by_user(request.user)
1146
1147 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1148     list_display = ("name", )
1149     user_readonly_fields = ['name']
1150     user_readonly_inlines = []
1151
1152 class RouterAdmin(PlanetStackBaseAdmin):
1153     list_display = ("name", )
1154     user_readonly_fields = ['name']
1155     user_readonly_inlines = []
1156
1157 class RouterROInline(ReadOnlyTabularInline):
1158     model = Router.networks.through
1159     extra = 0
1160     verbose_name_plural = "Routers"
1161     verbose_name = "Router"
1162     suit_classes = 'suit-tab suit-tab-routers'
1163
1164     fields = ['name', 'owner', 'permittedNetworks', 'networks']
1165
1166 class RouterInline(PlStackTabularInline):
1167     model = Router.networks.through
1168     extra = 0
1169     verbose_name_plural = "Routers"
1170     verbose_name = "Router"
1171     suit_classes = 'suit-tab suit-tab-routers'
1172
1173 class NetworkParameterROInline(ReadOnlyTabularInline):
1174     model = NetworkParameter
1175     extra = 1
1176     verbose_name_plural = "Parameters"
1177     verbose_name = "Parameter"
1178     suit_classes = 'suit-tab suit-tab-netparams'
1179     fields = ['parameter', 'value', 'content_type', 'object_id', 'content_object']
1180
1181 class NetworkParameterInline(generic.GenericTabularInline):
1182     model = NetworkParameter
1183     extra = 1
1184     verbose_name_plural = "Parameters"
1185     verbose_name = "Parameter"
1186     suit_classes = 'suit-tab suit-tab-netparams'
1187
1188 class NetworkSliversROInline(ReadOnlyTabularInline):
1189     fields = ['network', 'sliver', 'ip', 'port_id']
1190     model = NetworkSliver
1191     extra = 0
1192     verbose_name_plural = "Slivers"
1193     verbose_name = "Sliver"
1194     suit_classes = 'suit-tab suit-tab-networkslivers'
1195
1196 class NetworkSliversInline(PlStackTabularInline):
1197     readonly_fields = ("ip", )
1198     model = NetworkSliver
1199     selflink_fieldname = "sliver"
1200     extra = 0
1201     verbose_name_plural = "Slivers"
1202     verbose_name = "Sliver"
1203     suit_classes = 'suit-tab suit-tab-networkslivers'
1204
1205 class NetworkSlicesROInline(ReadOnlyTabularInline):
1206     model = NetworkSlice
1207     extra = 0
1208     verbose_name_plural = "Slices"
1209     verbose_name = "Slice"
1210     suit_classes = 'suit-tab suit-tab-networkslices'
1211     fields = ['network','slice']
1212
1213 class NetworkSlicesInline(PlStackTabularInline):
1214     model = NetworkSlice
1215     selflink_fieldname = "slice"
1216     extra = 0
1217     verbose_name_plural = "Slices"
1218     verbose_name = "Slice"
1219     suit_classes = 'suit-tab suit-tab-networkslices'
1220
1221 class NetworkAdmin(PlanetStackBaseAdmin):
1222     list_display = ("name", "subnet", "ports", "labels")
1223     readonly_fields = ("subnet", )
1224
1225     inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1226
1227     fieldsets = [
1228         (None, {'fields': ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet'], 'classes':['suit-tab suit-tab-general']}),]
1229
1230     user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1231     user_readonly_inlines = [NetworkParameterROInline, NetworkSliversROInline, NetworkSlicesROInline, RouterROInline]
1232
1233     suit_form_tabs =(
1234         ('general','Network Details'),
1235         ('netparams', 'Parameters'),
1236         ('networkslivers','Slivers'),
1237         ('networkslices','Slices'),
1238         ('routers','Routers'),
1239     )
1240 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1241     list_display = ("name", "guaranteedBandwidth", "visibility")
1242     user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1243     user_readonly_inlines = []
1244
1245 # register a signal that caches the user's credentials when they log in
1246 def cache_credentials(sender, user, request, **kwds):
1247     auth = {'username': request.POST['username'],
1248             'password': request.POST['password']}
1249     request.session['auth'] = auth
1250 user_logged_in.connect(cache_credentials)
1251
1252 def dollar_field(fieldName, short_description):
1253     def newFunc(self, obj):
1254         try:
1255             x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1256         except:
1257             x=getattr(obj, fieldName, 0.0)
1258         return x
1259     newFunc.short_description = short_description
1260     return newFunc
1261
1262 def right_dollar_field(fieldName, short_description):
1263     def newFunc(self, obj):
1264         try:
1265             #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1266             x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1267         except:
1268             x=getattr(obj, fieldName, 0.0)
1269         return x
1270     newFunc.short_description = short_description
1271     newFunc.allow_tags = True
1272     return newFunc
1273
1274 class InvoiceChargeInline(PlStackTabularInline):
1275     model = Charge
1276     extra = 0
1277     verbose_name_plural = "Charges"
1278     verbose_name = "Charge"
1279     exclude = ['account']
1280     fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1281     readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1282     can_delete = False
1283     max_num = 0
1284
1285     dollar_amount = right_dollar_field("amount", "Amount")
1286
1287 class InvoiceAdmin(admin.ModelAdmin):
1288     list_display = ("date", "account")
1289
1290     inlines = [InvoiceChargeInline]
1291
1292     fields = ["date", "account", "dollar_amount"]
1293     readonly_fields = ["date", "account", "dollar_amount"]
1294
1295     dollar_amount = dollar_field("amount", "Amount")
1296
1297 class InvoiceInline(PlStackTabularInline):
1298     model = Invoice
1299     extra = 0
1300     verbose_name_plural = "Invoices"
1301     verbose_name = "Invoice"
1302     fields = ["date", "dollar_amount"]
1303     readonly_fields = ["date", "dollar_amount"]
1304     suit_classes = 'suit-tab suit-tab-accountinvoice'
1305     can_delete=False
1306     max_num=0
1307
1308     dollar_amount = right_dollar_field("amount", "Amount")
1309
1310 class PendingChargeInline(PlStackTabularInline):
1311     model = Charge
1312     extra = 0
1313     verbose_name_plural = "Charges"
1314     verbose_name = "Charge"
1315     exclude = ["invoice"]
1316     fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1317     readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1318     suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1319     can_delete=False
1320     max_num=0
1321
1322     def queryset(self, request):
1323         qs = super(PendingChargeInline, self).queryset(request)
1324         qs = qs.filter(state="pending")
1325         return qs
1326
1327     dollar_amount = right_dollar_field("amount", "Amount")
1328
1329 class PaymentInline(PlStackTabularInline):
1330     model=Payment
1331     extra = 1
1332     verbose_name_plural = "Payments"
1333     verbose_name = "Payment"
1334     fields = ["date", "dollar_amount"]
1335     readonly_fields = ["date", "dollar_amount"]
1336     suit_classes = 'suit-tab suit-tab-accountpayments'
1337     can_delete=False
1338     max_num=0
1339
1340     dollar_amount = right_dollar_field("amount", "Amount")
1341
1342 class AccountAdmin(admin.ModelAdmin):
1343     list_display = ("site", "balance_due")
1344
1345     inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1346
1347     fieldsets = [
1348         (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1349
1350     readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1351
1352     suit_form_tabs =(
1353         ('general','Account Details'),
1354         ('accountinvoice', 'Invoices'),
1355         ('accountpayments', 'Payments'),
1356         ('accountpendingcharges','Pending Charges'),
1357     )
1358
1359     dollar_balance_due = dollar_field("balance_due", "Balance Due")
1360     dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1361     dollar_total_payments = dollar_field("total_payments", "Total Payments")
1362
1363
1364 # Now register the new UserAdmin...
1365 admin.site.register(User, UserAdmin)
1366 # ... and, since we're not using Django's builtin permissions,
1367 # unregister the Group model from admin.
1368 #admin.site.unregister(Group)
1369
1370 #Do not show django evolution in the admin interface
1371 from django_evolution.models import Version, Evolution
1372 #admin.site.unregister(Version)
1373 #admin.site.unregister(Evolution)
1374
1375
1376 # When debugging it is often easier to see all the classes, but for regular use 
1377 # only the top-levels should be displayed
1378 showAll = False
1379
1380 admin.site.register(Deployment, DeploymentAdmin)
1381 admin.site.register(Site, SiteAdmin)
1382 admin.site.register(Slice, SliceAdmin)
1383 admin.site.register(Service, ServiceAdmin)
1384 admin.site.register(Reservation, ReservationAdmin)
1385 admin.site.register(Network, NetworkAdmin)
1386 admin.site.register(Router, RouterAdmin)
1387 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1388 admin.site.register(Account, AccountAdmin)
1389 admin.site.register(Invoice, InvoiceAdmin)
1390
1391 if True:
1392     admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1393     admin.site.register(ServiceClass, ServiceClassAdmin)
1394     #admin.site.register(PlanetStack)
1395     admin.site.register(Tag, TagAdmin)
1396     admin.site.register(DeploymentRole)
1397     admin.site.register(SiteRole)
1398     admin.site.register(SliceRole)
1399     admin.site.register(PlanetStackRole)
1400     admin.site.register(Node, NodeAdmin)
1401     #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1402     #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1403     admin.site.register(Sliver, SliverAdmin)
1404     admin.site.register(Image, ImageAdmin)
1405     admin.site.register(DashboardView, DashboardViewAdmin)
1406