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