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