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