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