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