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