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