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