fix nonworking tabs displayed when adding user
[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_id = cleaned_data.get('site')
731         site = Slice.objects.get(id=site_id)
732         if not name.startswith(site.login_base):
733             raise forms.ValidationError('slice name must begin with %s' % site.login_base)
734         return cleaned_data
735
736 class SliceAdmin(PlanetStackBaseAdmin):
737     form = SliceForm
738     fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
739     fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
740     readonly_fields = ('backend_status_text', )
741     list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_slivers')
742     list_display_links = ('backend_status_icon', 'name', )
743     inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
744
745     user_readonly_fields = fieldList
746
747     suit_form_tabs =(('general', 'Slice Details'),
748         ('slicenetworks','Networks'),
749         ('sliceprivileges','Privileges'),
750         ('slivers','Slivers'),
751         ('tags','Tags'),
752         ('reservations','Reservations'),
753     )
754
755     def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
756         deployment_nodes = []
757         for node in Node.objects.all():
758             deployment_nodes.append( (node.deployment.id, node.id, node.name) )
759
760         deployment_flavors = []
761         for flavor in Flavor.objects.all():
762             for deployment in flavor.deployments.all():
763                 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
764
765         deployment_images = []
766         for image in Image.objects.all():
767             for imageDeployment in image.imagedeployments_set.all():
768                 deployment_images.append( (imageDeployment.deployment.id, image.id, image.name) )
769
770         site_login_bases = []
771         for site in Site.objects.all():
772             site_login_bases.append((site.id, site.login_base))
773
774         context["deployment_nodes"] = deployment_nodes
775         context["deployment_flavors"] = deployment_flavors
776         context["deployment_images"] = deployment_images
777         context["site_login_bases"] = site_login_bases
778         return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
779
780     def formfield_for_foreignkey(self, db_field, request, **kwargs):
781         if db_field.name == 'site':
782             kwargs['queryset'] = Site.select_by_user(request.user)
783             kwargs['widget'] = forms.Select(attrs={'onChange': "update_slice_prefix(this, $($(this).closest('fieldset')[0]).find('.field-name input')[0].id)"})
784
785         return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
786
787     def queryset(self, request):
788         # admins can see all keys. Users can only see slices they belong to.
789         return Slice.select_by_user(request.user)
790
791     def get_formsets(self, request, obj=None):
792         for inline in self.get_inline_instances(request, obj):
793             # hide MyInline in the add view
794             if obj is None:
795                 continue
796             if isinstance(inline, SliverInline):
797                 inline.model.caller = request.user
798             yield inline.get_formset(request, obj)
799
800
801 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
802     fieldsets = [
803         (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
804     ]
805     readonly_fields = ('backend_status_text', )
806     list_display = ('backend_status_icon', 'user', 'slice', 'role')
807     list_display_links = list_display
808
809     user_readonly_fields = ['user', 'slice', 'role']
810     user_readonly_inlines = []
811
812     def formfield_for_foreignkey(self, db_field, request, **kwargs):
813         if db_field.name == 'slice':
814             kwargs['queryset'] = Slice.select_by_user(request.user)
815         
816         if db_field.name == 'user':
817             kwargs['queryset'] = User.select_by_user(request.user)
818
819         return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
820
821     def queryset(self, request):
822         # admins can see all memberships. Users can only see memberships of
823         # slices where they have the admin role.
824         return SlicePrivilege.select_by_user(request.user)
825
826     def save_model(self, request, obj, form, change):
827         # update openstack connection to use this site/tenant
828         auth = request.session.get('auth', {})
829         auth['tenant'] = obj.slice.slicename
830         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
831         obj.save()
832
833     def delete_model(self, request, obj):
834         # update openstack connection to use this site/tenant
835         auth = request.session.get('auth', {})
836         auth['tenant'] = obj.slice.slicename
837         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
838         obj.delete()
839
840
841 class ImageAdmin(PlanetStackBaseAdmin):
842
843     fieldsets = [('Image Details',
844                    {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
845                     'classes': ['suit-tab suit-tab-general']})
846                ]
847     readonly_fields = ('backend_status_text', )
848
849     suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
850
851     inlines = [SliverInline, ImageDeploymentsInline]
852
853     user_readonly_fields = ['name', 'disk_format', 'container_format']
854
855     list_display = ['backend_status_icon', 'name']
856     list_display_links = ('backend_status_icon', 'name', )
857
858 class NodeForm(forms.ModelForm):
859     class Meta:
860         widgets = {
861             'site': LinkedSelect,
862             'deployment': LinkedSelect
863         }
864
865 class NodeAdmin(PlanetStackBaseAdmin):
866     form = NodeForm
867     list_display = ('backend_status_icon', 'name', 'site', 'deployment')
868     list_display_links = ('backend_status_icon', 'name', )
869     list_filter = ('deployment',)
870
871     inlines = [TagInline,SliverInline]
872     fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
873     readonly_fields = ('backend_status_text', )
874
875     user_readonly_fields = ['name','site','deployment']
876     user_readonly_inlines = [TagInline,SliverInline]
877
878     suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
879
880
881 class SliverForm(forms.ModelForm):
882     class Meta:
883         model = Sliver
884         ip = forms.CharField(widget=PlainTextWidget)
885         instance_name = forms.CharField(widget=PlainTextWidget)
886         widgets = {
887             'ip': PlainTextWidget(),
888             'instance_name': PlainTextWidget(),
889             'slice': LinkedSelect,
890             'deploymentNetwork': LinkedSelect,
891             'node': LinkedSelect,
892             'image': LinkedSelect
893         }
894
895 class TagAdmin(PlanetStackBaseAdmin):
896     list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
897     list_display_links = list_display
898     user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
899     user_readonly_inlines = []
900
901 class SliverAdmin(PlanetStackBaseAdmin):
902     form = SliverForm
903     fieldsets = [
904         ('Sliver Details', {'fields': ['backend_status_text', 'slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
905     ]
906     readonly_fields = ('backend_status_text', )
907     list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'flavor', 'image', 'node', 'deploymentNetwork']
908     list_display_links = ('backend_status_icon', 'ip',)
909
910     suit_form_tabs =(('general', 'Sliver Details'),
911         ('tags','Tags'),
912     )
913
914     inlines = [TagInline]
915
916     user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image']
917
918     def formfield_for_foreignkey(self, db_field, request, **kwargs):
919         if db_field.name == 'slice':
920             kwargs['queryset'] = Slice.select_by_user(request.user)
921
922         return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
923
924     def queryset(self, request):
925         # admins can see all slivers. Users can only see slivers of
926         # the slices they belong to.
927         return Sliver.select_by_user(request.user)
928
929
930     def get_formsets(self, request, obj=None):
931         # make some fields read only if we are updating an existing record
932         if obj == None:
933             #self.readonly_fields = ('ip', 'instance_name')
934             self.readonly_fields = ('backend_status_text')
935         else:
936             self.readonly_fields = ('backend_status_text')
937             #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
938
939         for inline in self.get_inline_instances(request, obj):
940             # hide MyInline in the add view
941             if obj is None:
942                 continue
943             if isinstance(inline, SliverInline):
944                 inline.model.caller = request.user
945             yield inline.get_formset(request, obj)
946
947     #def save_model(self, request, obj, form, change):
948     #    # update openstack connection to use this site/tenant
949     #    auth = request.session.get('auth', {})
950     #    auth['tenant'] = obj.slice.name
951     #    obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
952     #    obj.creator = request.user
953     #    obj.save()
954
955     #def delete_model(self, request, obj):
956     #    # update openstack connection to use this site/tenant
957     #    auth = request.session.get('auth', {})
958     #    auth['tenant'] = obj.slice.name
959     #    obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
960     #    obj.delete()
961
962 class UserCreationForm(forms.ModelForm):
963     """A form for creating new users. Includes all the required
964     fields, plus a repeated password."""
965     password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
966     password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
967
968     class Meta:
969         model = User
970         fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
971
972     def clean_password2(self):
973         # Check that the two password entries match
974         password1 = self.cleaned_data.get("password1")
975         password2 = self.cleaned_data.get("password2")
976         if password1 and password2 and password1 != password2:
977             raise forms.ValidationError("Passwords don't match")
978         return password2
979
980     def save(self, commit=True):
981         # Save the provided password in hashed format
982         user = super(UserCreationForm, self).save(commit=False)
983         user.password = self.cleaned_data["password1"]
984         #user.set_password(self.cleaned_data["password1"])
985         if commit:
986             user.save()
987         return user
988
989
990 class UserChangeForm(forms.ModelForm):
991     """A form for updating users. Includes all the fields on
992     the user, but replaces the password field with admin's
993     password hash display field.
994     """
995     password = ReadOnlyPasswordHashField(label='Password',
996                    help_text= '<a href=\"password/\">Change Password</a>.')
997
998     class Meta:
999         model = User
1000
1001     def clean_password(self):
1002         # Regardless of what the user provides, return the initial value.
1003         # This is done here, rather than on the field, because the
1004         # field does not have access to the initial value
1005         return self.initial["password"]
1006
1007 class UserDashboardViewInline(PlStackTabularInline):
1008     model = UserDashboardView
1009     extra = 0
1010     suit_classes = 'suit-tab suit-tab-dashboards'
1011     fields = ['user', 'dashboardView', 'order']
1012
1013 class UserAdmin(PermissionCheckingAdminMixin, UserAdmin):
1014     # Note: Make sure PermissionCheckingAdminMixin is listed before
1015     # admin.ModelAdmin in the class declaration.
1016
1017     class Meta:
1018         app_label = "core"
1019
1020     # The forms to add and change user instances
1021     form = UserChangeForm
1022     add_form = UserCreationForm
1023
1024     # The fields to be used in displaying the User model.
1025     # These override the definitions on the base UserAdmin
1026     # that reference specific fields on auth.User.
1027     list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
1028     list_filter = ('site',)
1029     inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
1030
1031     fieldListLoginDetails = ['backend_status_text', 'email','site','password','is_active','is_readonly','is_admin','public_key']
1032     fieldListContactInfo = ['firstname','lastname','phone','timezone']
1033
1034     fieldsets = (
1035         ('Login Details', {'fields': ['backend_status_text', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1036         ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1037         #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1038         #('Important dates', {'fields': ('last_login',)}),
1039     )
1040     add_fieldsets = (
1041         (None, {
1042             'classes': ('wide',),
1043             'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')},
1044         ),
1045     )
1046     readonly_fields = ('backend_status_text', )
1047     search_fields = ('email',)
1048     ordering = ('email',)
1049     filter_horizontal = ()
1050
1051     user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1052
1053     def get_form(self, request, obj=None):
1054         # Save obj in thread-local storage, so suit_form_tabs can use it to
1055         # determine whether we're in edit or add mode.
1056         _thread_locals.request = request
1057         _thread_locals.obj = obj
1058         return super(UserAdmin, self).get_form(request, obj)
1059
1060     @property
1061     def suit_form_tabs(self):
1062         if getattr(_thread_locals, "obj", None) is None:
1063             return []
1064         else:
1065             return (('general','Login Details'),
1066                          ('contact','Contact Information'),
1067                          ('sliceprivileges','Slice Privileges'),
1068                          ('siteprivileges','Site Privileges'),
1069                          ('deploymentprivileges','Deployment Privileges'),
1070                          ('dashboards','Dashboard Views'))
1071
1072     def formfield_for_foreignkey(self, db_field, request, **kwargs):
1073         if db_field.name == 'site':
1074             kwargs['queryset'] = Site.select_by_user(request.user)
1075
1076         return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1077
1078     def queryset(self, request):
1079         return User.select_by_user(request.user)
1080
1081 class DashboardViewAdmin(PlanetStackBaseAdmin):
1082     fieldsets = [('Dashboard View Details',
1083                    {'fields': ['backend_status_text', 'name', 'url'],
1084                     'classes': ['suit-tab suit-tab-general']})
1085                ]
1086     readonly_fields = ('backend_status_text', )
1087
1088     suit_form_tabs =(('general','Dashboard View Details'),)
1089
1090 class ServiceResourceInline(PlStackTabularInline):
1091     model = ServiceResource
1092     extra = 0
1093
1094 class ServiceClassAdmin(PlanetStackBaseAdmin):
1095     list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1096     list_display_links = ('backend_status_icon', 'name', )
1097     inlines = [ServiceResourceInline]
1098
1099     user_readonly_fields = ['name', 'commitment', 'membershipFee']
1100     user_readonly_inlines = []
1101
1102 class ReservedResourceInline(PlStackTabularInline):
1103     model = ReservedResource
1104     extra = 0
1105     suit_classes = 'suit-tab suit-tab-reservedresources'
1106
1107     def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1108         field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1109
1110         if db_field.name == 'resource':
1111             # restrict resources to those that the slice's service class allows
1112             if request._slice is not None:
1113                 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1114                 if len(field.queryset) > 0:
1115                     field.initial = field.queryset.all()[0]
1116             else:\r
1117                 field.queryset = field.queryset.none()\r
1118         elif db_field.name == 'sliver':\r
1119             # restrict slivers to those that belong to the slice\r
1120             if request._slice is not None:\r
1121                 field.queryset = field.queryset.filter(slice = request._slice)
1122             else:
1123                 field.queryset = field.queryset.none()\r
1124 \r
1125         return field
1126
1127     def queryset(self, request):
1128         return ReservedResource.select_by_user(request.user)
1129
1130 class ReservationChangeForm(forms.ModelForm):
1131     class Meta:
1132         model = Reservation
1133         widgets = {
1134             'slice' : LinkedSelect
1135         }
1136
1137 class ReservationAddForm(forms.ModelForm):
1138     slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1139     refresh = forms.CharField(widget=forms.HiddenInput())
1140
1141     class Media:
1142        css = {'all': ('planetstack.css',)}   # .field-refresh { display: none; }
1143
1144     def clean_slice(self):
1145         slice = self.cleaned_data.get("slice")
1146         x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1147         if len(x) == 0:
1148             raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1149         return slice
1150
1151     class Meta:
1152         model = Reservation
1153         widgets = {
1154             'slice' : LinkedSelect
1155         }
1156
1157
1158 class ReservationAddRefreshForm(ReservationAddForm):
1159     """ This form is displayed when the Reservation Form receives an update
1160         from the Slice dropdown onChange handler. It doesn't validate the
1161         data and doesn't save the data. This will cause the form to be
1162         redrawn.
1163     """
1164
1165     """ don't validate anything other than slice """
1166     dont_validate_fields = ("startTime", "duration")
1167
1168     def full_clean(self):
1169         result = super(ReservationAddForm, self).full_clean()
1170
1171         for fieldname in self.dont_validate_fields:
1172             if fieldname in self._errors:
1173                 del self._errors[fieldname]
1174
1175         return result
1176
1177     """ don't save anything """
1178     def is_valid(self):
1179         return False
1180
1181 class ReservationAdmin(PlanetStackBaseAdmin):
1182     fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
1183     fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1184     readonly_fields = ('backend_status_text', )
1185     list_display = ('startTime', 'duration')
1186     form = ReservationAddForm
1187
1188     suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1189
1190     inlines = [ReservedResourceInline]
1191     user_readonly_fields = fieldList
1192
1193     def add_view(self, request, form_url='', extra_context=None):
1194         timezone.activate(request.user.timezone)
1195         request._refresh = False
1196         request._slice = None
1197         if request.method == 'POST':
1198             # "refresh" will be set to "1" if the form was submitted due to
1199             # a change in the Slice dropdown.
1200             if request.POST.get("refresh","1") == "1":
1201                 request._refresh = True
1202                 request.POST["refresh"] = "0"
1203
1204             # Keep track of the slice that was selected, so the
1205             # reservedResource inline can filter items for the slice.
1206             request._slice = request.POST.get("slice",None)
1207             if (request._slice is not None):
1208                 request._slice = Slice.objects.get(id=request._slice)
1209
1210         result =  super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1211         return result
1212
1213     def changelist_view(self, request, extra_context = None):
1214         timezone.activate(request.user.timezone)
1215         return super(ReservationAdmin, self).changelist_view(request, extra_context)
1216
1217     def get_form(self, request, obj=None, **kwargs):
1218         request._obj_ = obj
1219         if obj is not None:
1220             # For changes, set request._slice to the slice already set in the
1221             # object.
1222             request._slice = obj.slice
1223             self.form = ReservationChangeForm
1224         else:
1225             if getattr(request, "_refresh", False):
1226                 self.form = ReservationAddRefreshForm
1227             else:
1228                 self.form = ReservationAddForm
1229         return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1230
1231     def get_readonly_fields(self, request, obj=None):
1232         if (obj is not None):
1233             # Prevent slice from being changed after the reservation has been
1234             # created.
1235             return ['slice']
1236         else:
1237             return []
1238
1239     def queryset(self, request):
1240         return Reservation.select_by_user(request.user)
1241
1242 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1243     list_display = ("backend_status_icon", "name", )
1244     list_display_links = ('backend_status_icon', 'name', )
1245     user_readonly_fields = ['name']
1246     user_readonly_inlines = []
1247
1248 class RouterAdmin(PlanetStackBaseAdmin):
1249     list_display = ("backend_status_icon", "name", )
1250     list_display_links = ('backend_status_icon', 'name', )
1251     user_readonly_fields = ['name']
1252     user_readonly_inlines = []
1253
1254 class RouterInline(PlStackTabularInline):
1255     model = Router.networks.through
1256     extra = 0
1257     verbose_name_plural = "Routers"
1258     verbose_name = "Router"
1259     suit_classes = 'suit-tab suit-tab-routers'
1260
1261 class NetworkParameterInline(PlStackGenericTabularInline):
1262     model = NetworkParameter
1263     extra = 0
1264     verbose_name_plural = "Parameters"
1265     verbose_name = "Parameter"
1266     suit_classes = 'suit-tab suit-tab-netparams'
1267     fields = ['backend_status_icon', 'parameter', 'value']
1268     readonly_fields = ('backend_status_icon', )
1269
1270 class NetworkSliversInline(PlStackTabularInline):
1271     fields = ['backend_status_icon', 'network','sliver','ip']
1272     readonly_fields = ("backend_status_icon", "ip", )
1273     model = NetworkSliver
1274     selflink_fieldname = "sliver"
1275     extra = 0
1276     verbose_name_plural = "Slivers"
1277     verbose_name = "Sliver"
1278     suit_classes = 'suit-tab suit-tab-networkslivers'
1279
1280 class NetworkSlicesInline(PlStackTabularInline):
1281     model = NetworkSlice
1282     selflink_fieldname = "slice"
1283     extra = 0
1284     verbose_name_plural = "Slices"
1285     verbose_name = "Slice"
1286     suit_classes = 'suit-tab suit-tab-networkslices'
1287     fields = ['backend_status_icon', 'network','slice']
1288     readonly_fields = ('backend_status_icon', )
1289
1290 class NetworkAdmin(PlanetStackBaseAdmin):
1291     list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1292     list_display_links = ('backend_status_icon', 'name', )
1293     readonly_fields = ("subnet", )
1294
1295     inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1296
1297     fieldsets = [
1298         (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']}),]
1299
1300     readonly_fields = ('backend_status_text', )
1301     user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1302
1303     suit_form_tabs =(
1304         ('general','Network Details'),
1305         ('netparams', 'Parameters'),
1306         ('networkslivers','Slivers'),
1307         ('networkslices','Slices'),
1308         ('routers','Routers'),
1309     )
1310 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1311     list_display = ("backend_status_icon", "name", "guaranteedBandwidth", "visibility")
1312     list_display_links = ('backend_status_icon', 'name', )
1313     user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1314     user_readonly_inlines = []
1315
1316 class FlavorAdmin(PlanetStackBaseAdmin):
1317     list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1318     list_display_links = ("backend_status_icon", "name")
1319     user_readonly_fields = ("name", "flavor")
1320     fields = ("name", "description", "flavor", "order", "default")
1321
1322 # register a signal that caches the user's credentials when they log in
1323 def cache_credentials(sender, user, request, **kwds):
1324     auth = {'username': request.POST['username'],
1325             'password': request.POST['password']}
1326     request.session['auth'] = auth
1327 user_logged_in.connect(cache_credentials)
1328
1329 def dollar_field(fieldName, short_description):
1330     def newFunc(self, obj):
1331         try:
1332             x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1333         except:
1334             x=getattr(obj, fieldName, 0.0)
1335         return x
1336     newFunc.short_description = short_description
1337     return newFunc
1338
1339 def right_dollar_field(fieldName, short_description):
1340     def newFunc(self, obj):
1341         try:
1342             #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1343             x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1344         except:
1345             x=getattr(obj, fieldName, 0.0)
1346         return x
1347     newFunc.short_description = short_description
1348     newFunc.allow_tags = True
1349     return newFunc
1350
1351 class InvoiceChargeInline(PlStackTabularInline):
1352     model = Charge
1353     extra = 0
1354     verbose_name_plural = "Charges"
1355     verbose_name = "Charge"
1356     exclude = ['account']
1357     fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1358     readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1359     can_delete = False
1360     max_num = 0
1361
1362     dollar_amount = right_dollar_field("amount", "Amount")
1363
1364 class InvoiceAdmin(admin.ModelAdmin):
1365     list_display = ("date", "account")
1366
1367     inlines = [InvoiceChargeInline]
1368
1369     fields = ["date", "account", "dollar_amount"]
1370     readonly_fields = ["date", "account", "dollar_amount"]
1371
1372     dollar_amount = dollar_field("amount", "Amount")
1373
1374 class InvoiceInline(PlStackTabularInline):
1375     model = Invoice
1376     extra = 0
1377     verbose_name_plural = "Invoices"
1378     verbose_name = "Invoice"
1379     fields = ["date", "dollar_amount"]
1380     readonly_fields = ["date", "dollar_amount"]
1381     suit_classes = 'suit-tab suit-tab-accountinvoice'
1382     can_delete=False
1383     max_num=0
1384
1385     dollar_amount = right_dollar_field("amount", "Amount")
1386
1387 class PendingChargeInline(PlStackTabularInline):
1388     model = Charge
1389     extra = 0
1390     verbose_name_plural = "Charges"
1391     verbose_name = "Charge"
1392     exclude = ["invoice"]
1393     fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1394     readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1395     suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1396     can_delete=False
1397     max_num=0
1398
1399     def queryset(self, request):
1400         qs = super(PendingChargeInline, self).queryset(request)
1401         qs = qs.filter(state="pending")
1402         return qs
1403
1404     dollar_amount = right_dollar_field("amount", "Amount")
1405
1406 class PaymentInline(PlStackTabularInline):
1407     model=Payment
1408     extra = 1
1409     verbose_name_plural = "Payments"
1410     verbose_name = "Payment"
1411     fields = ["date", "dollar_amount"]
1412     readonly_fields = ["date", "dollar_amount"]
1413     suit_classes = 'suit-tab suit-tab-accountpayments'
1414     can_delete=False
1415     max_num=0
1416
1417     dollar_amount = right_dollar_field("amount", "Amount")
1418
1419 class AccountAdmin(admin.ModelAdmin):
1420     list_display = ("site", "balance_due")
1421
1422     inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1423
1424     fieldsets = [
1425         (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1426
1427     readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1428
1429     suit_form_tabs =(
1430         ('general','Account Details'),
1431         ('accountinvoice', 'Invoices'),
1432         ('accountpayments', 'Payments'),
1433         ('accountpendingcharges','Pending Charges'),
1434     )
1435
1436     dollar_balance_due = dollar_field("balance_due", "Balance Due")
1437     dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1438     dollar_total_payments = dollar_field("total_payments", "Total Payments")
1439
1440 # Now register the new UserAdmin...
1441 admin.site.register(User, UserAdmin)
1442 # ... and, since we're not using Django's builtin permissions,
1443 # unregister the Group model from admin.
1444 #admin.site.unregister(Group)
1445
1446 #Do not show django evolution in the admin interface
1447 from django_evolution.models import Version, Evolution
1448 #admin.site.unregister(Version)
1449 #admin.site.unregister(Evolution)
1450
1451
1452 # When debugging it is often easier to see all the classes, but for regular use 
1453 # only the top-levels should be displayed
1454 showAll = False
1455
1456 admin.site.register(Deployment, DeploymentAdmin)
1457 admin.site.register(Site, SiteAdmin)
1458 admin.site.register(Slice, SliceAdmin)
1459 admin.site.register(Service, ServiceAdmin)
1460 admin.site.register(Reservation, ReservationAdmin)
1461 admin.site.register(Network, NetworkAdmin)
1462 admin.site.register(Router, RouterAdmin)
1463 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1464 admin.site.register(Account, AccountAdmin)
1465 admin.site.register(Invoice, InvoiceAdmin)
1466
1467 if True:
1468     admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1469     admin.site.register(ServiceClass, ServiceClassAdmin)
1470     #admin.site.register(PlanetStack)
1471     admin.site.register(Tag, TagAdmin)
1472     admin.site.register(DeploymentRole)
1473     admin.site.register(SiteRole)
1474     admin.site.register(SliceRole)
1475     admin.site.register(PlanetStackRole)
1476     admin.site.register(Node, NodeAdmin)
1477     #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1478     #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1479     admin.site.register(Sliver, SliverAdmin)
1480     admin.site.register(Image, ImageAdmin)
1481     admin.site.register(DashboardView, DashboardViewAdmin)
1482     admin.site.register(Flavor, FlavorAdmin)
1483