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