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