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