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