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