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