escape backend_status strings
[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 render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
786         deployment_nodes = []
787         for node in Node.objects.all():
788             deployment_nodes.append( (node.deployment.id, node.id, node.name) )
789
790         deployment_flavors = []
791         for flavor in Flavor.objects.all():
792             for deployment in flavor.deployments.all():
793                 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
794
795         deployment_images = []
796         for image in Image.objects.all():
797             for imageDeployment in image.imagedeployments_set.all():
798                 deployment_images.append( (imageDeployment.deployment.id, image.id, image.name) )
799
800         site_login_bases = []
801         for site in Site.objects.all():
802             site_login_bases.append((site.id, site.login_base))
803
804         context["deployment_nodes"] = deployment_nodes
805         context["deployment_flavors"] = deployment_flavors
806         context["deployment_images"] = deployment_images
807         context["site_login_bases"] = site_login_bases
808         return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
809
810     def formfield_for_foreignkey(self, db_field, request, **kwargs):
811         if db_field.name == 'site':
812             kwargs['queryset'] = Site.select_by_user(request.user)
813             kwargs['widget'] = forms.Select(attrs={'onChange': "update_slice_prefix(this, $($(this).closest('fieldset')[0]).find('.field-name input')[0].id)"})
814
815         return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
816
817     def queryset(self, request):
818         # admins can see all keys. Users can only see slices they belong to.
819         return Slice.select_by_user(request.user)
820
821     def get_formsets(self, request, obj=None):
822         for inline in self.get_inline_instances(request, obj):
823             # hide MyInline in the add view
824             if obj is None:
825                 continue
826             if isinstance(inline, SliverInline):
827                 inline.model.caller = request.user
828             yield inline.get_formset(request, obj)
829
830 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
831     fieldsets = [
832         (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
833     ]
834     readonly_fields = ('backend_status_text', )
835     list_display = ('backend_status_icon', 'user', 'slice', 'role')
836     list_display_links = list_display
837
838     user_readonly_fields = ['user', 'slice', 'role']
839     user_readonly_inlines = []
840
841     def formfield_for_foreignkey(self, db_field, request, **kwargs):
842         if db_field.name == 'slice':
843             kwargs['queryset'] = Slice.select_by_user(request.user)
844         
845         if db_field.name == 'user':
846             kwargs['queryset'] = User.select_by_user(request.user)
847
848         return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
849
850     def queryset(self, request):
851         # admins can see all memberships. Users can only see memberships of
852         # slices where they have the admin role.
853         return SlicePrivilege.select_by_user(request.user)
854
855     def save_model(self, request, obj, form, change):
856         # update openstack connection to use this site/tenant
857         auth = request.session.get('auth', {})
858         auth['tenant'] = obj.slice.slicename
859         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
860         obj.save()
861
862     def delete_model(self, request, obj):
863         # update openstack connection to use this site/tenant
864         auth = request.session.get('auth', {})
865         auth['tenant'] = obj.slice.slicename
866         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
867         obj.delete()
868
869
870 class ImageAdmin(PlanetStackBaseAdmin):
871
872     fieldsets = [('Image Details',
873                    {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
874                     'classes': ['suit-tab suit-tab-general']})
875                ]
876     readonly_fields = ('backend_status_text', )
877
878     suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
879
880     inlines = [SliverInline, ImageDeploymentsInline]
881
882     user_readonly_fields = ['name', 'disk_format', 'container_format']
883
884     list_display = ['backend_status_icon', 'name']
885     list_display_links = ('backend_status_icon', 'name', )
886
887 class NodeForm(forms.ModelForm):
888     class Meta:
889         widgets = {
890             'site': LinkedSelect,
891             'deployment': LinkedSelect
892         }
893
894 class NodeAdmin(PlanetStackBaseAdmin):
895     form = NodeForm
896     list_display = ('backend_status_icon', 'name', 'site', 'deployment')
897     list_display_links = ('backend_status_icon', 'name', )
898     list_filter = ('deployment',)
899
900     inlines = [TagInline,SliverInline]
901     fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
902     readonly_fields = ('backend_status_text', )
903
904     user_readonly_fields = ['name','site','deployment']
905     user_readonly_inlines = [TagInline,SliverInline]
906
907     suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
908
909
910 class SliverForm(forms.ModelForm):
911     class Meta:
912         model = Sliver
913         ip = forms.CharField(widget=PlainTextWidget)
914         instance_name = forms.CharField(widget=PlainTextWidget)
915         widgets = {
916             'ip': PlainTextWidget(),
917             'instance_name': PlainTextWidget(),
918             'slice': LinkedSelect,
919             'deploymentNetwork': LinkedSelect,
920             'node': LinkedSelect,
921             'image': LinkedSelect
922         }
923
924 class TagAdmin(PlanetStackBaseAdmin):
925     list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
926     list_display_links = list_display
927     user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
928     user_readonly_inlines = []
929
930 class SliverAdmin(PlanetStackBaseAdmin):
931     form = SliverForm
932     fieldsets = [
933         ('Sliver Details', {'fields': ['backend_status_text', 'slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
934     ]
935     readonly_fields = ('backend_status_text', )
936     list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'flavor', 'image', 'node', 'deploymentNetwork']
937     list_display_links = ('backend_status_icon', 'ip',)
938
939     suit_form_tabs =(('general', 'Sliver Details'),
940         ('tags','Tags'),
941     )
942
943     inlines = [TagInline]
944
945     user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image']
946
947     def formfield_for_foreignkey(self, db_field, request, **kwargs):
948         if db_field.name == 'slice':
949             kwargs['queryset'] = Slice.select_by_user(request.user)
950
951         return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
952
953     def queryset(self, request):
954         # admins can see all slivers. Users can only see slivers of
955         # the slices they belong to.
956         return Sliver.select_by_user(request.user)
957
958
959     def get_formsets(self, request, obj=None):
960         # make some fields read only if we are updating an existing record
961         if obj == None:
962             #self.readonly_fields = ('ip', 'instance_name')
963             self.readonly_fields = ('backend_status_text',)
964         else:
965             self.readonly_fields = ('backend_status_text',)
966             #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
967
968         for inline in self.get_inline_instances(request, obj):
969             # hide MyInline in the add view
970             if obj is None:
971                 continue
972             if isinstance(inline, SliverInline):
973                 inline.model.caller = request.user
974             yield inline.get_formset(request, obj)
975
976     #def save_model(self, request, obj, form, change):
977     #    # update openstack connection to use this site/tenant
978     #    auth = request.session.get('auth', {})
979     #    auth['tenant'] = obj.slice.name
980     #    obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
981     #    obj.creator = request.user
982     #    obj.save()
983
984     #def delete_model(self, request, obj):
985     #    # update openstack connection to use this site/tenant
986     #    auth = request.session.get('auth', {})
987     #    auth['tenant'] = obj.slice.name
988     #    obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
989     #    obj.delete()
990
991 class UserCreationForm(forms.ModelForm):
992     """A form for creating new users. Includes all the required
993     fields, plus a repeated password."""
994     password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
995     password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
996
997     class Meta:
998         model = User
999         fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
1000
1001     def clean_password2(self):
1002         # Check that the two password entries match
1003         password1 = self.cleaned_data.get("password1")
1004         password2 = self.cleaned_data.get("password2")
1005         if password1 and password2 and password1 != password2:
1006             raise forms.ValidationError("Passwords don't match")
1007         return password2
1008
1009     def save(self, commit=True):
1010         # Save the provided password in hashed format
1011         user = super(UserCreationForm, self).save(commit=False)
1012         user.password = self.cleaned_data["password1"]
1013         #user.set_password(self.cleaned_data["password1"])
1014         if commit:
1015             user.save()
1016         return user
1017
1018
1019 class UserChangeForm(forms.ModelForm):
1020     """A form for updating users. Includes all the fields on
1021     the user, but replaces the password field with admin's
1022     password hash display field.
1023     """
1024     password = ReadOnlyPasswordHashField(label='Password',
1025                    help_text= '<a href=\"password/\">Change Password</a>.')
1026
1027     class Meta:
1028         model = User
1029
1030     def clean_password(self):
1031         # Regardless of what the user provides, return the initial value.
1032         # This is done here, rather than on the field, because the
1033         # field does not have access to the initial value
1034         return self.initial["password"]
1035
1036 class UserDashboardViewInline(PlStackTabularInline):
1037     model = UserDashboardView
1038     extra = 0
1039     suit_classes = 'suit-tab suit-tab-dashboards'
1040     fields = ['user', 'dashboardView', 'order']
1041
1042 class UserAdmin(PermissionCheckingAdminMixin, UserAdmin):
1043     # Note: Make sure PermissionCheckingAdminMixin is listed before
1044     # admin.ModelAdmin in the class declaration.
1045
1046     class Meta:
1047         app_label = "core"
1048
1049     # The forms to add and change user instances
1050     form = UserChangeForm
1051     add_form = UserCreationForm
1052
1053     # The fields to be used in displaying the User model.
1054     # These override the definitions on the base UserAdmin
1055     # that reference specific fields on auth.User.
1056     list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
1057     list_filter = ('site',)
1058     inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
1059
1060     fieldListLoginDetails = ['backend_status_text', 'email','site','password','is_active','is_readonly','is_admin','public_key']
1061     fieldListContactInfo = ['firstname','lastname','phone','timezone']
1062
1063     fieldsets = (
1064         ('Login Details', {'fields': ['backend_status_text', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1065         ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1066         #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1067         #('Important dates', {'fields': ('last_login',)}),
1068     )
1069     add_fieldsets = (
1070         (None, {
1071             'classes': ('wide',),
1072             'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')},
1073         ),
1074     )
1075     readonly_fields = ('backend_status_text', )
1076     search_fields = ('email',)
1077     ordering = ('email',)
1078     filter_horizontal = ()
1079
1080     user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1081
1082     @property
1083     def suit_form_tabs(self):
1084         if getattr(_thread_locals, "obj", None) is None:
1085             return []
1086         else:
1087             return (('general','Login Details'),
1088                          ('contact','Contact Information'),
1089                          ('sliceprivileges','Slice Privileges'),
1090                          ('siteprivileges','Site Privileges'),
1091                          ('deploymentprivileges','Deployment Privileges'),
1092                          ('dashboards','Dashboard Views'))
1093
1094     def formfield_for_foreignkey(self, db_field, request, **kwargs):
1095         if db_field.name == 'site':
1096             kwargs['queryset'] = Site.select_by_user(request.user)
1097
1098         return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1099
1100     def queryset(self, request):
1101         return User.select_by_user(request.user)
1102
1103 class DashboardViewAdmin(PlanetStackBaseAdmin):
1104     fieldsets = [('Dashboard View Details',
1105                    {'fields': ['backend_status_text', 'name', 'url'],
1106                     'classes': ['suit-tab suit-tab-general']})
1107                ]
1108     readonly_fields = ('backend_status_text', )
1109
1110     suit_form_tabs =(('general','Dashboard View Details'),)
1111
1112 class ServiceResourceInline(PlStackTabularInline):
1113     model = ServiceResource
1114     extra = 0
1115
1116 class ServiceClassAdmin(PlanetStackBaseAdmin):
1117     list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1118     list_display_links = ('backend_status_icon', 'name', )
1119     inlines = [ServiceResourceInline]
1120
1121     user_readonly_fields = ['name', 'commitment', 'membershipFee']
1122     user_readonly_inlines = []
1123
1124 class ReservedResourceInline(PlStackTabularInline):
1125     model = ReservedResource
1126     extra = 0
1127     suit_classes = 'suit-tab suit-tab-reservedresources'
1128
1129     def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1130         field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1131
1132         if db_field.name == 'resource':
1133             # restrict resources to those that the slice's service class allows
1134             if request._slice is not None:
1135                 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1136                 if len(field.queryset) > 0:
1137                     field.initial = field.queryset.all()[0]
1138             else:\r
1139                 field.queryset = field.queryset.none()\r
1140         elif db_field.name == 'sliver':\r
1141             # restrict slivers to those that belong to the slice\r
1142             if request._slice is not None:\r
1143                 field.queryset = field.queryset.filter(slice = request._slice)
1144             else:
1145                 field.queryset = field.queryset.none()\r
1146 \r
1147         return field
1148
1149     def queryset(self, request):
1150         return ReservedResource.select_by_user(request.user)
1151
1152 class ReservationChangeForm(forms.ModelForm):
1153     class Meta:
1154         model = Reservation
1155         widgets = {
1156             'slice' : LinkedSelect
1157         }
1158
1159 class ReservationAddForm(forms.ModelForm):
1160     slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1161     refresh = forms.CharField(widget=forms.HiddenInput())
1162
1163     class Media:
1164        css = {'all': ('planetstack.css',)}   # .field-refresh { display: none; }
1165
1166     def clean_slice(self):
1167         slice = self.cleaned_data.get("slice")
1168         x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1169         if len(x) == 0:
1170             raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1171         return slice
1172
1173     class Meta:
1174         model = Reservation
1175         widgets = {
1176             'slice' : LinkedSelect
1177         }
1178
1179
1180 class ReservationAddRefreshForm(ReservationAddForm):
1181     """ This form is displayed when the Reservation Form receives an update
1182         from the Slice dropdown onChange handler. It doesn't validate the
1183         data and doesn't save the data. This will cause the form to be
1184         redrawn.
1185     """
1186
1187     """ don't validate anything other than slice """
1188     dont_validate_fields = ("startTime", "duration")
1189
1190     def full_clean(self):
1191         result = super(ReservationAddForm, self).full_clean()
1192
1193         for fieldname in self.dont_validate_fields:
1194             if fieldname in self._errors:
1195                 del self._errors[fieldname]
1196
1197         return result
1198
1199     """ don't save anything """
1200     def is_valid(self):
1201         return False
1202
1203 class ReservationAdmin(PlanetStackBaseAdmin):
1204     fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
1205     fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1206     readonly_fields = ('backend_status_text', )
1207     list_display = ('startTime', 'duration')
1208     form = ReservationAddForm
1209
1210     suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1211
1212     inlines = [ReservedResourceInline]
1213     user_readonly_fields = fieldList
1214
1215     def add_view(self, request, form_url='', extra_context=None):
1216         timezone.activate(request.user.timezone)
1217         request._refresh = False
1218         request._slice = None
1219         if request.method == 'POST':
1220             # "refresh" will be set to "1" if the form was submitted due to
1221             # a change in the Slice dropdown.
1222             if request.POST.get("refresh","1") == "1":
1223                 request._refresh = True
1224                 request.POST["refresh"] = "0"
1225
1226             # Keep track of the slice that was selected, so the
1227             # reservedResource inline can filter items for the slice.
1228             request._slice = request.POST.get("slice",None)
1229             if (request._slice is not None):
1230                 request._slice = Slice.objects.get(id=request._slice)
1231
1232         result =  super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1233         return result
1234
1235     def changelist_view(self, request, extra_context = None):
1236         timezone.activate(request.user.timezone)
1237         return super(ReservationAdmin, self).changelist_view(request, extra_context)
1238
1239     def get_form(self, request, obj=None, **kwargs):
1240         request._obj_ = obj
1241         if obj is not None:
1242             # For changes, set request._slice to the slice already set in the
1243             # object.
1244             request._slice = obj.slice
1245             self.form = ReservationChangeForm
1246         else:
1247             if getattr(request, "_refresh", False):
1248                 self.form = ReservationAddRefreshForm
1249             else:
1250                 self.form = ReservationAddForm
1251         return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1252
1253     def get_readonly_fields(self, request, obj=None):
1254         if (obj is not None):
1255             # Prevent slice from being changed after the reservation has been
1256             # created.
1257             return ['slice']
1258         else:
1259             return []
1260
1261     def queryset(self, request):
1262         return Reservation.select_by_user(request.user)
1263
1264 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1265     list_display = ("backend_status_icon", "name", )
1266     list_display_links = ('backend_status_icon', 'name', )
1267     user_readonly_fields = ['name']
1268     user_readonly_inlines = []
1269
1270 class RouterAdmin(PlanetStackBaseAdmin):
1271     list_display = ("backend_status_icon", "name", )
1272     list_display_links = ('backend_status_icon', 'name', )
1273     user_readonly_fields = ['name']
1274     user_readonly_inlines = []
1275
1276 class RouterInline(PlStackTabularInline):
1277     model = Router.networks.through
1278     extra = 0
1279     verbose_name_plural = "Routers"
1280     verbose_name = "Router"
1281     suit_classes = 'suit-tab suit-tab-routers'
1282
1283 class NetworkParameterInline(PlStackGenericTabularInline):
1284     model = NetworkParameter
1285     extra = 0
1286     verbose_name_plural = "Parameters"
1287     verbose_name = "Parameter"
1288     suit_classes = 'suit-tab suit-tab-netparams'
1289     fields = ['backend_status_icon', 'parameter', 'value']
1290     readonly_fields = ('backend_status_icon', )
1291
1292 class NetworkSliversInline(PlStackTabularInline):
1293     fields = ['backend_status_icon', 'network','sliver','ip']
1294     readonly_fields = ("backend_status_icon", "ip", )
1295     model = NetworkSliver
1296     selflink_fieldname = "sliver"
1297     extra = 0
1298     verbose_name_plural = "Slivers"
1299     verbose_name = "Sliver"
1300     suit_classes = 'suit-tab suit-tab-networkslivers'
1301
1302 class NetworkSlicesInline(PlStackTabularInline):
1303     model = NetworkSlice
1304     selflink_fieldname = "slice"
1305     extra = 0
1306     verbose_name_plural = "Slices"
1307     verbose_name = "Slice"
1308     suit_classes = 'suit-tab suit-tab-networkslices'
1309     fields = ['backend_status_icon', 'network','slice']
1310     readonly_fields = ('backend_status_icon', )
1311
1312 class NetworkDeploymentsInline(PlStackTabularInline):
1313     model = NetworkDeployments
1314     extra = 0
1315     verbose_name_plural = "Network Deployments"
1316     verbose_name = "Network Deployment"
1317     suit_classes = 'suit-tab suit-tab-admin-only'
1318     fields = ['backend_status_icon', 'deployment','net_id','subnet_id']
1319     readonly_fields = ('backend_status_icon', )
1320
1321 class NetworkAdmin(PlanetStackBaseAdmin):
1322     list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1323     list_display_links = ('backend_status_icon', 'name', )
1324     readonly_fields = ("subnet", )
1325
1326     inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1327     admin_inlines = [NetworkDeploymentsInline]
1328
1329     fieldsets = [
1330         (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']}),]
1331
1332     readonly_fields = ('backend_status_text', )
1333     user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1334
1335     @property
1336     def suit_form_tabs(self):
1337         tabs=[('general','Network Details'),
1338             ('netparams', 'Parameters'),
1339             ('networkslivers','Slivers'),
1340             ('networkslices','Slices'),
1341             ('routers','Routers'),
1342         ]
1343
1344         request=getattr(_thread_locals, "request", None)
1345         if request and request.user.is_admin:
1346             tabs.append( ('admin-only', 'Admin-Only') )
1347
1348         return tabs
1349
1350
1351 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1352     list_display = ("backend_status_icon", "name", "guaranteedBandwidth", "visibility")
1353     list_display_links = ('backend_status_icon', 'name', )
1354     user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1355     user_readonly_inlines = []
1356
1357 class FlavorAdmin(PlanetStackBaseAdmin):
1358     list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1359     list_display_links = ("backend_status_icon", "name")
1360     user_readonly_fields = ("name", "flavor")
1361     fields = ("name", "description", "flavor", "order", "default")
1362
1363 # register a signal that caches the user's credentials when they log in
1364 def cache_credentials(sender, user, request, **kwds):
1365     auth = {'username': request.POST['username'],
1366             'password': request.POST['password']}
1367     request.session['auth'] = auth
1368 user_logged_in.connect(cache_credentials)
1369
1370 def dollar_field(fieldName, short_description):
1371     def newFunc(self, obj):
1372         try:
1373             x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1374         except:
1375             x=getattr(obj, fieldName, 0.0)
1376         return x
1377     newFunc.short_description = short_description
1378     return newFunc
1379
1380 def right_dollar_field(fieldName, short_description):
1381     def newFunc(self, obj):
1382         try:
1383             #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1384             x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1385         except:
1386             x=getattr(obj, fieldName, 0.0)
1387         return x
1388     newFunc.short_description = short_description
1389     newFunc.allow_tags = True
1390     return newFunc
1391
1392 class InvoiceChargeInline(PlStackTabularInline):
1393     model = Charge
1394     extra = 0
1395     verbose_name_plural = "Charges"
1396     verbose_name = "Charge"
1397     exclude = ['account']
1398     fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1399     readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1400     can_delete = False
1401     max_num = 0
1402
1403     dollar_amount = right_dollar_field("amount", "Amount")
1404
1405 class InvoiceAdmin(admin.ModelAdmin):
1406     list_display = ("date", "account")
1407
1408     inlines = [InvoiceChargeInline]
1409
1410     fields = ["date", "account", "dollar_amount"]
1411     readonly_fields = ["date", "account", "dollar_amount"]
1412
1413     dollar_amount = dollar_field("amount", "Amount")
1414
1415 class InvoiceInline(PlStackTabularInline):
1416     model = Invoice
1417     extra = 0
1418     verbose_name_plural = "Invoices"
1419     verbose_name = "Invoice"
1420     fields = ["date", "dollar_amount"]
1421     readonly_fields = ["date", "dollar_amount"]
1422     suit_classes = 'suit-tab suit-tab-accountinvoice'
1423     can_delete=False
1424     max_num=0
1425
1426     dollar_amount = right_dollar_field("amount", "Amount")
1427
1428 class PendingChargeInline(PlStackTabularInline):
1429     model = Charge
1430     extra = 0
1431     verbose_name_plural = "Charges"
1432     verbose_name = "Charge"
1433     exclude = ["invoice"]
1434     fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1435     readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1436     suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1437     can_delete=False
1438     max_num=0
1439
1440     def queryset(self, request):
1441         qs = super(PendingChargeInline, self).queryset(request)
1442         qs = qs.filter(state="pending")
1443         return qs
1444
1445     dollar_amount = right_dollar_field("amount", "Amount")
1446
1447 class PaymentInline(PlStackTabularInline):
1448     model=Payment
1449     extra = 1
1450     verbose_name_plural = "Payments"
1451     verbose_name = "Payment"
1452     fields = ["date", "dollar_amount"]
1453     readonly_fields = ["date", "dollar_amount"]
1454     suit_classes = 'suit-tab suit-tab-accountpayments'
1455     can_delete=False
1456     max_num=0
1457
1458     dollar_amount = right_dollar_field("amount", "Amount")
1459
1460 class AccountAdmin(admin.ModelAdmin):
1461     list_display = ("site", "balance_due")
1462
1463     inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1464
1465     fieldsets = [
1466         (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1467
1468     readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1469
1470     suit_form_tabs =(
1471         ('general','Account Details'),
1472         ('accountinvoice', 'Invoices'),
1473         ('accountpayments', 'Payments'),
1474         ('accountpendingcharges','Pending Charges'),
1475     )
1476
1477     dollar_balance_due = dollar_field("balance_due", "Balance Due")
1478     dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1479     dollar_total_payments = dollar_field("total_payments", "Total Payments")
1480
1481 # Now register the new UserAdmin...
1482 admin.site.register(User, UserAdmin)
1483 # ... and, since we're not using Django's builtin permissions,
1484 # unregister the Group model from admin.
1485 #admin.site.unregister(Group)
1486
1487 #Do not show django evolution in the admin interface
1488 from django_evolution.models import Version, Evolution
1489 #admin.site.unregister(Version)
1490 #admin.site.unregister(Evolution)
1491
1492
1493 # When debugging it is often easier to see all the classes, but for regular use 
1494 # only the top-levels should be displayed
1495 showAll = False
1496
1497 admin.site.register(Deployment, DeploymentAdmin)
1498 admin.site.register(Site, SiteAdmin)
1499 admin.site.register(Slice, SliceAdmin)
1500 admin.site.register(Service, ServiceAdmin)
1501 admin.site.register(Reservation, ReservationAdmin)
1502 admin.site.register(Network, NetworkAdmin)
1503 admin.site.register(Router, RouterAdmin)
1504 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1505 admin.site.register(Account, AccountAdmin)
1506 admin.site.register(Invoice, InvoiceAdmin)
1507
1508 if True:
1509     admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1510     admin.site.register(ServiceClass, ServiceClassAdmin)
1511     #admin.site.register(PlanetStack)
1512     admin.site.register(Tag, TagAdmin)
1513     admin.site.register(DeploymentRole)
1514     admin.site.register(SiteRole)
1515     admin.site.register(SliceRole)
1516     admin.site.register(PlanetStackRole)
1517     admin.site.register(Node, NodeAdmin)
1518     #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1519     #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1520     admin.site.register(Sliver, SliverAdmin)
1521     admin.site.register(Image, ImageAdmin)
1522     admin.site.register(DashboardView, DashboardViewAdmin)
1523     admin.site.register(Flavor, FlavorAdmin)
1524