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