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