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