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