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