remove Tags tab from model pages
[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.sites.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_deployment 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 class ServiceAttrAsTabInline(PlStackTabularInline):
746     model = ServiceAttribute
747     fields = ['name','value']
748     extra = 0
749     suit_classes = 'suit-tab suit-tab-serviceattrs'
750
751 class ServiceAdmin(PlanetStackBaseAdmin):
752     list_display = ("backend_status_icon","name","description","versionNumber","enabled","published")
753     list_display_links = ('backend_status_icon', 'name', )
754     fieldList = ["backend_status_text","name","description","versionNumber","enabled","published"]
755     fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
756     inlines = [ServiceAttrAsTabInline,SliceInline]
757     readonly_fields = ('backend_status_text', )
758
759     user_readonly_fields = fieldList
760
761     suit_form_tabs =(('general', 'Service Details'),
762         ('slices','Slices'),
763         ('serviceattrs','Additional Attributes'),
764     )
765
766 class SiteAdmin(PlanetStackBaseAdmin):
767     fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
768     fieldsets = [
769         (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
770         #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
771     ]
772     readonly_fields = ['backend_status_text', 'accountLink']
773
774     user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
775
776     list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
777     list_display_links = ('backend_status_icon', 'name', )
778     filter_horizontal = ('deployments',)
779     inlines = [SliceInline,UserInline,TagInline, SitePrivilegeInline, SiteDeploymentInline]
780     admin_inlines = [ControllerSite]
781     search_fields = ['name']
782
783     @property
784     def suit_form_tabs(self):
785         tabs = [('general', 'Site Details'),
786             ('users','Users'),
787             ('siteprivileges','Privileges'),
788             ('deployments','Deployments'),
789             ('slices','Slices'),
790             #('nodes','Nodes'),
791         ]
792
793         request=getattr(_thread_locals, "request", None)
794         if request and request.user.is_admin:
795             tabs.append( ('admin-only', 'Admin-Only') )
796
797         return tabs
798
799     def queryset(self, request):
800         return Site.select_by_user(request.user)
801
802     def get_formsets(self, request, obj=None):
803         for inline in self.get_inline_instances(request, obj):
804             # hide MyInline in the add view
805             if obj is None:
806                 continue
807             if isinstance(inline, SliverInline):
808                 inline.model.caller = request.user
809             yield inline.get_formset(request, obj)
810
811     def accountLink(self, obj):
812         link_obj = obj.accounts.all()
813         if link_obj:
814             reverse_path = "admin:core_account_change"
815             url = reverse(reverse_path, args =(link_obj[0].id,))
816             return "<a href='%s'>%s</a>" % (url, "view billing details")
817         else:
818             return "no billing data for this site"
819     accountLink.allow_tags = True
820     accountLink.short_description = "Billing"
821
822     def save_model(self, request, obj, form, change):
823         # update openstack connection to use this site/tenant
824         obj.save_by_user(request.user) 
825
826     def delete_model(self, request, obj):
827         obj.delete_by_user(request.user)
828         
829
830 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
831     fieldList = ['backend_status_text', 'user', 'site', 'role']
832     fieldsets = [
833         (None, {'fields': fieldList, 'classes':['collapse']})
834     ]
835     readonly_fields = ('backend_status_text', )
836     list_display = ('backend_status_icon', 'user', 'site', 'role')
837     list_display_links = list_display
838     user_readonly_fields = fieldList
839     user_readonly_inlines = []
840
841     def formfield_for_foreignkey(self, db_field, request, **kwargs):
842         if db_field.name == 'site':
843             if not request.user.is_admin:
844                 # only show sites where user is an admin or pi
845                 sites = set()
846                 for site_privilege in SitePrivilege.objects.filer(user=request.user):
847                     if site_privilege.role.role_type in ['admin', 'pi']:
848                         sites.add(site_privilege.site)
849                 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
850
851         if db_field.name == 'user':
852             if not request.user.is_admin:
853                 # only show users from sites where caller has admin or pi role
854                 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
855                 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
856                 sites = [site_privilege.site for site_privilege in site_privileges]
857                 site_privileges = SitePrivilege.objects.filter(site__in=sites)
858                 emails = [site_privilege.user.email for site_privilege in site_privileges]
859                 users = User.objects.filter(email__in=emails)
860                 kwargs['queryset'] = users
861
862         return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
863
864     def queryset(self, request):
865         # admins can see all privileges. Users can only see privileges at sites
866         # where they have the admin role or pi role.
867         qs = super(SitePrivilegeAdmin, self).queryset(request)
868         #if not request.user.is_admin:
869         #    roles = Role.objects.filter(role_type__in=['admin', 'pi'])
870         #    site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
871         #    login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
872         #    sites = Site.objects.filter(login_base__in=login_bases)
873         #    qs = qs.filter(site__in=sites)
874         return qs
875
876 class SliceForm(forms.ModelForm):
877     class Meta:
878         model = Slice
879         widgets = {
880             'service': LinkedSelect
881         }
882
883     def clean(self):
884         cleaned_data = super(SliceForm, self).clean()
885         name = cleaned_data.get('name')
886         site = cleaned_data.get('site')
887         slice_id = self.instance.id
888         if not site and slice_id:
889             site = Slice.objects.get(id=slice_id).site
890         if (not isinstance(site,Site)):
891             # previous code indicates 'site' could be a site_id and not a site?
892             site = Slice.objects.get(id=site.id)
893         if not name.startswith(site.login_base):
894             raise forms.ValidationError('slice name must begin with %s' % site.login_base)
895         return cleaned_data
896
897 class ControllerSliceInline(PlStackTabularInline):
898     model = ControllerSlice
899     extra = 0
900     verbose_name = "Controller Slices"
901     verbose_name_plural = "Controller Slices"
902     suit_classes = 'suit-tab suit-tab-admin-only'
903     fields = ['backend_status_icon', 'controller', 'tenant_id']
904     readonly_fields = ('backend_status_icon', 'controller' )
905
906 class SliceAdmin(PlanetStackBaseAdmin):
907     form = SliceForm
908     fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
909     fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
910     readonly_fields = ('backend_status_text', )
911     list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_slivers')
912     list_display_links = ('backend_status_icon', 'name', )
913     inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
914     admin_inlines = [ControllerSliceInline]
915
916     user_readonly_fields = fieldList
917
918     @property
919     def suit_form_tabs(self):
920         tabs =[('general', 'Slice Details'),
921           ('slicenetworks','Networks'),
922           ('sliceprivileges','Privileges'),
923           ('slivers','Slivers'),
924           ('reservations','Reservations'),
925           ]
926
927         request=getattr(_thread_locals, "request", None)
928         if request and request.user.is_admin:
929             tabs.append( ('admin-only', 'Admin-Only') )
930
931         return tabs
932     
933     def add_view(self, request, form_url='', extra_context=None):
934         # revert to default read-only fields
935         self.readonly_fields = ('backend_status_text',)
936         return super(SliceAdmin, self).add_view(request, form_url, extra_context=extra_context)
937
938     def change_view(self, request, object_id, form_url='', extra_context=None):
939         # cannot change the site of an existing slice so make the site field read only
940         if object_id:
941             self.readonly_fields = ('backend_status_text','site')
942         return super(SliceAdmin, self).change_view(request, object_id, form_url)
943
944     def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
945         deployment_nodes = []
946         for node in Node.objects.all():
947             deployment_nodes.append( (node.site_deployment.id, node.id, node.name) )
948
949         deployment_flavors = []
950         for flavor in Flavor.objects.all():
951             for deployment in flavor.deployments.all():
952                 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
953
954         deployment_images = []
955         for image in Image.objects.all():
956             for deployment_image in image.imagedeployments.all():
957                 deployment_images.append( (deployment_image.deployment.id, image.id, image.name) )
958
959         site_login_bases = []
960         for site in Site.objects.all():
961             site_login_bases.append((site.id, site.login_base))
962
963         context["deployment_nodes"] = deployment_nodes
964         context["deployment_flavors"] = deployment_flavors
965         context["deployment_images"] = deployment_images
966         context["site_login_bases"] = site_login_bases
967         return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
968
969     def formfield_for_foreignkey(self, db_field, request, **kwargs):
970         if db_field.name == 'site':
971             kwargs['queryset'] = Site.select_by_user(request.user)
972             kwargs['widget'] = forms.Select(attrs={'onChange': "update_slice_prefix(this, $($(this).closest('fieldset')[0]).find('.field-name input')[0].id)"})
973
974         return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
975
976     def queryset(self, request):
977         # admins can see all keys. Users can only see slices they belong to.
978         return Slice.select_by_user(request.user)
979
980     def get_formsets(self, request, obj=None):
981         for inline in self.get_inline_instances(request, obj):
982             # hide MyInline in the add view
983             if obj is None:
984                 continue
985             if isinstance(inline, SliverInline):
986                 inline.model.caller = request.user
987             yield inline.get_formset(request, obj)
988
989 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
990     fieldsets = [
991         (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
992     ]
993     readonly_fields = ('backend_status_text', )
994     list_display = ('backend_status_icon', 'user', 'slice', 'role')
995     list_display_links = list_display
996
997     user_readonly_fields = ['user', 'slice', 'role']
998     user_readonly_inlines = []
999
1000     def formfield_for_foreignkey(self, db_field, request, **kwargs):
1001         if db_field.name == 'slice':
1002             kwargs['queryset'] = Slice.select_by_user(request.user)
1003         
1004         if db_field.name == 'user':
1005             kwargs['queryset'] = User.select_by_user(request.user)
1006
1007         return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1008
1009     def queryset(self, request):
1010         # admins can see all memberships. Users can only see memberships of
1011         # slices where they have the admin role.
1012         return SlicePrivilege.select_by_user(request.user)
1013
1014     def save_model(self, request, obj, form, change):
1015         # update openstack connection to use this site/tenant
1016         auth = request.session.get('auth', {})
1017         auth['tenant'] = obj.slice.slicename
1018         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1019         obj.save()
1020
1021     def delete_model(self, request, obj):
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.delete()
1027
1028
1029 class ImageAdmin(PlanetStackBaseAdmin):
1030
1031     fieldsets = [('Image Details',
1032                    {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
1033                     'classes': ['suit-tab suit-tab-general']})
1034                ]
1035     readonly_fields = ('backend_status_text', )
1036
1037     suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'), ('controllerimages', 'Controllers'))
1038
1039     inlines = [SliverInline, ControllerImagesInline]
1040
1041     user_readonly_fields = ['name', 'disk_format', 'container_format']
1042
1043     list_display = ['backend_status_icon', 'name']
1044     list_display_links = ('backend_status_icon', 'name', )
1045
1046 class NodeForm(forms.ModelForm):
1047     class Meta:
1048         widgets = {
1049             'site': LinkedSelect,
1050             'deployment': LinkedSelect
1051         }
1052
1053 class NodeAdmin(PlanetStackBaseAdmin):
1054     form = NodeForm
1055     list_display = ('backend_status_icon', 'name', 'site_deployment')
1056     list_display_links = ('backend_status_icon', 'name', )
1057     list_filter = ('site_deployment',)
1058
1059     inlines = [TagInline,SliverInline]
1060     fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site_deployment'], 'classes':['suit-tab suit-tab-details']})]
1061     readonly_fields = ('backend_status_text', )
1062
1063     user_readonly_fields = ['name','site_deployment']
1064     user_readonly_inlines = [TagInline,SliverInline]
1065
1066     suit_form_tabs =(('details','Node Details'),('slivers','Slivers'))
1067
1068
1069 class SliverForm(forms.ModelForm):
1070     class Meta:
1071         model = Sliver
1072         ip = forms.CharField(widget=PlainTextWidget)
1073         instance_name = forms.CharField(widget=PlainTextWidget)
1074         widgets = {
1075             'ip': PlainTextWidget(),
1076             'instance_name': PlainTextWidget(),
1077             'slice': LinkedSelect,
1078             'deployment': LinkedSelect,
1079             'node': LinkedSelect,
1080             'image': LinkedSelect
1081         }
1082
1083 class TagAdmin(PlanetStackBaseAdmin):
1084     list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
1085     list_display_links = list_display
1086     user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
1087     user_readonly_inlines = []
1088
1089 class SliverAdmin(PlanetStackBaseAdmin):
1090     form = SliverForm
1091     fieldsets = [
1092         ('Sliver Details', {'fields': ['backend_status_text', 'slice', 'deployment', 'node', 'ip', 'instance_name', 'flavor', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
1093     ]
1094     readonly_fields = ('backend_status_text', )
1095     list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'flavor', 'image', 'node', 'deployment']
1096     list_display_links = ('backend_status_icon', 'ip',)
1097
1098     suit_form_tabs =(('general', 'Sliver Details'))
1099
1100     inlines = [TagInline]
1101
1102     user_readonly_fields = ['slice', 'deployment', 'node', 'ip', 'instance_name', 'flavor', 'image']
1103
1104     def formfield_for_foreignkey(self, db_field, request, **kwargs):
1105         if db_field.name == 'slice':
1106             kwargs['queryset'] = Slice.select_by_user(request.user)
1107
1108         return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1109
1110     def queryset(self, request):
1111         # admins can see all slivers. Users can only see slivers of
1112         # the slices they belong to.
1113         return Sliver.select_by_user(request.user)
1114
1115
1116     def get_formsets(self, request, obj=None):
1117         # make some fields read only if we are updating an existing record
1118         if obj == None:
1119             #self.readonly_fields = ('ip', 'instance_name')
1120             self.readonly_fields = ('backend_status_text',)
1121         else:
1122             self.readonly_fields = ('backend_status_text',)
1123             #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
1124
1125         for inline in self.get_inline_instances(request, obj):
1126             # hide MyInline in the add view
1127             if obj is None:
1128                 continue
1129             if isinstance(inline, SliverInline):
1130                 inline.model.caller = request.user
1131             yield inline.get_formset(request, obj)
1132
1133     #def save_model(self, request, obj, form, change):
1134     #    # update openstack connection to use this site/tenant
1135     #    auth = request.session.get('auth', {})
1136     #    auth['tenant'] = obj.slice.name
1137     #    obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1138     #    obj.creator = request.user
1139     #    obj.save()
1140
1141     #def delete_model(self, request, obj):
1142     #    # update openstack connection to use this site/tenant
1143     #    auth = request.session.get('auth', {})
1144     #    auth['tenant'] = obj.slice.name
1145     #    obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1146     #    obj.delete()
1147
1148 class UserCreationForm(forms.ModelForm):
1149     """A form for creating new users. Includes all the required
1150     fields, plus a repeated password."""
1151     password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
1152     password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
1153
1154     class Meta:
1155         model = User
1156         fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
1157
1158     def clean_password2(self):
1159         # Check that the two password entries match
1160         password1 = self.cleaned_data.get("password1")
1161         password2 = self.cleaned_data.get("password2")
1162         if password1 and password2 and password1 != password2:
1163             raise forms.ValidationError("Passwords don't match")
1164         return password2
1165
1166     def save(self, commit=True):
1167         # Save the provided password in hashed format
1168         user = super(UserCreationForm, self).save(commit=False)
1169         user.password = self.cleaned_data["password1"]
1170         #user.set_password(self.cleaned_data["password1"])
1171         if commit:
1172             user.save()
1173         return user
1174
1175
1176 class UserChangeForm(forms.ModelForm):
1177     """A form for updating users. Includes all the fields on
1178     the user, but replaces the password field with admin's
1179     password hash display field.
1180     """
1181     password = ReadOnlyPasswordHashField(label='Password',
1182                    help_text= '<a href=\"password/\">Change Password</a>.')
1183
1184     class Meta:
1185         model = User
1186         widgets = { 'public_key': UploadTextareaWidget, }
1187
1188     def clean_password(self):
1189         # Regardless of what the user provides, return the initial value.
1190         # This is done here, rather than on the field, because the
1191         # field does not have access to the initial value
1192         return self.initial["password"]
1193
1194 class UserDashboardViewInline(PlStackTabularInline):
1195     model = UserDashboardView
1196     extra = 0
1197     suit_classes = 'suit-tab suit-tab-dashboards'
1198     fields = ['user', 'dashboardView', 'order']
1199
1200 class ControllerUserInline(PlStackTabularInline):
1201     model = ControllerUser
1202     extra = 0
1203     suit_classes = 'suit-tab suit-tab-admin-only'
1204     fields = ['controller', 'user', 'kuser_id']
1205     readonly_fields=['controller']
1206
1207
1208 class UserAdmin(PermissionCheckingAdminMixin, UserAdmin):
1209     # Note: Make sure PermissionCheckingAdminMixin is listed before
1210     # admin.ModelAdmin in the class declaration.
1211
1212     class Meta:
1213         app_label = "core"
1214
1215     # The forms to add and change user instances
1216     form = UserChangeForm
1217     add_form = UserCreationForm
1218
1219     # The fields to be used in displaying the User model.
1220     # These override the definitions on the base UserAdmin
1221     # that reference specific fields on auth.User.
1222     list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
1223     list_filter = ('site',)
1224     inlines = [SlicePrivilegeInline,SitePrivilegeInline,UserDashboardViewInline]
1225     admin_inlines = [ControllerUserInline]
1226     fieldListLoginDetails = ['backend_status_text', 'email','site','password','is_active','is_readonly','is_admin','public_key']
1227     fieldListContactInfo = ['firstname','lastname','phone','timezone']
1228
1229     fieldsets = (
1230         ('Login Details', {'fields': ['backend_status_text', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1231         ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1232         #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1233         #('Important dates', {'fields': ('last_login',)}),
1234     )
1235     add_fieldsets = (
1236         (None, {
1237             'classes': ('wide',),
1238             'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')},
1239         ),
1240     )
1241     readonly_fields = ('backend_status_text', )
1242     search_fields = ('email',)
1243     ordering = ('email',)
1244     filter_horizontal = ()
1245
1246     user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1247
1248     @property
1249     def suit_form_tabs(self):
1250         if getattr(_thread_locals, "obj", None) is None:
1251             return []
1252         else:
1253             tabs = [('general','Login Details'),
1254                          ('contact','Contact Information'),
1255                          ('sliceprivileges','Slice Privileges'),
1256                          ('siteprivileges','Site Privileges'),
1257                          ('dashboards','Dashboard Views')]
1258
1259             request=getattr(_thread_locals, "request", None)
1260             if request and request.user.is_admin:
1261                 tabs.append( ('admin-only', 'Admin-Only') )
1262
1263             return tabs
1264
1265     def formfield_for_foreignkey(self, db_field, request, **kwargs):
1266         if db_field.name == 'site':
1267             kwargs['queryset'] = Site.select_by_user(request.user)
1268
1269         return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1270
1271     def queryset(self, request):
1272         return User.select_by_user(request.user)
1273
1274 class ControllerDashboardViewInline(PlStackTabularInline):
1275     model = ControllerDashboardView
1276     extra = 0
1277     fields = ["controller", "url"]
1278     suit_classes = 'suit-tab suit-tab-controllers'
1279
1280 class DashboardViewAdmin(PlanetStackBaseAdmin):
1281     fieldsets = [('Dashboard View Details',
1282                    {'fields': ['backend_status_text', 'name', 'url'],
1283                     'classes': ['suit-tab suit-tab-general']})
1284                ]
1285     readonly_fields = ('backend_status_text', )
1286     inlines = [ControllerDashboardViewInline]
1287
1288     suit_form_tabs =(('general','Dashboard View Details'),
1289                      ('controllers', 'Per-controller Dashboard Details'))
1290
1291 class ServiceResourceInline(PlStackTabularInline):
1292     model = ServiceResource
1293     extra = 0
1294
1295 class ServiceClassAdmin(PlanetStackBaseAdmin):
1296     list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1297     list_display_links = ('backend_status_icon', 'name', )
1298     inlines = [ServiceResourceInline]
1299
1300     user_readonly_fields = ['name', 'commitment', 'membershipFee']
1301     user_readonly_inlines = []
1302
1303 class ReservedResourceInline(PlStackTabularInline):
1304     model = ReservedResource
1305     extra = 0
1306     suit_classes = 'suit-tab suit-tab-reservedresources'
1307
1308     def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1309         field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1310
1311         if db_field.name == 'resource':
1312             # restrict resources to those that the slice's service class allows
1313             if request._slice is not None:
1314                 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1315                 if len(field.queryset) > 0:
1316                     field.initial = field.queryset.all()[0]
1317             else:\r
1318                 field.queryset = field.queryset.none()\r
1319         elif db_field.name == 'sliver':\r
1320             # restrict slivers to those that belong to the slice\r
1321             if request._slice is not None:\r
1322                 field.queryset = field.queryset.filter(slice = request._slice)
1323             else:
1324                 field.queryset = field.queryset.none()\r
1325 \r
1326         return field
1327
1328     def queryset(self, request):
1329         return ReservedResource.select_by_user(request.user)
1330
1331 class ReservationChangeForm(forms.ModelForm):
1332     class Meta:
1333         model = Reservation
1334         widgets = {
1335             'slice' : LinkedSelect
1336         }
1337
1338 class ReservationAddForm(forms.ModelForm):
1339     slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1340     refresh = forms.CharField(widget=forms.HiddenInput())
1341
1342     class Media:
1343        css = {'all': ('planetstack.css',)}   # .field-refresh { display: none; }
1344
1345     def clean_slice(self):
1346         slice = self.cleaned_data.get("slice")
1347         x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1348         if len(x) == 0:
1349             raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1350         return slice
1351
1352     class Meta:
1353         model = Reservation
1354         widgets = {
1355             'slice' : LinkedSelect
1356         }
1357
1358
1359 class ReservationAddRefreshForm(ReservationAddForm):
1360     """ This form is displayed when the Reservation Form receives an update
1361         from the Slice dropdown onChange handler. It doesn't validate the
1362         data and doesn't save the data. This will cause the form to be
1363         redrawn.
1364     """
1365
1366     """ don't validate anything other than slice """
1367     dont_validate_fields = ("startTime", "duration")
1368
1369     def full_clean(self):
1370         result = super(ReservationAddForm, self).full_clean()
1371
1372         for fieldname in self.dont_validate_fields:
1373             if fieldname in self._errors:
1374                 del self._errors[fieldname]
1375
1376         return result
1377
1378     """ don't save anything """
1379     def is_valid(self):
1380         return False
1381
1382 class ReservationAdmin(PlanetStackBaseAdmin):
1383     fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
1384     fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1385     readonly_fields = ('backend_status_text', )
1386     list_display = ('startTime', 'duration')
1387     form = ReservationAddForm
1388
1389     suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1390
1391     inlines = [ReservedResourceInline]
1392     user_readonly_fields = fieldList
1393
1394     def add_view(self, request, form_url='', extra_context=None):
1395         timezone.activate(request.user.timezone)
1396         request._refresh = False
1397         request._slice = None
1398         if request.method == 'POST':
1399             # "refresh" will be set to "1" if the form was submitted due to
1400             # a change in the Slice dropdown.
1401             if request.POST.get("refresh","1") == "1":
1402                 request._refresh = True
1403                 request.POST["refresh"] = "0"
1404
1405             # Keep track of the slice that was selected, so the
1406             # reservedResource inline can filter items for the slice.
1407             request._slice = request.POST.get("slice",None)
1408             if (request._slice is not None):
1409                 request._slice = Slice.objects.get(id=request._slice)
1410
1411         result =  super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1412         return result
1413
1414     def changelist_view(self, request, extra_context = None):
1415         timezone.activate(request.user.timezone)
1416         return super(ReservationAdmin, self).changelist_view(request, extra_context)
1417
1418     def get_form(self, request, obj=None, **kwargs):
1419         request._obj_ = obj
1420         if obj is not None:
1421             # For changes, set request._slice to the slice already set in the
1422             # object.
1423             request._slice = obj.slice
1424             self.form = ReservationChangeForm
1425         else:
1426             if getattr(request, "_refresh", False):
1427                 self.form = ReservationAddRefreshForm
1428             else:
1429                 self.form = ReservationAddForm
1430         return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1431
1432     def get_readonly_fields(self, request, obj=None):
1433         if (obj is not None):
1434             # Prevent slice from being changed after the reservation has been
1435             # created.
1436             return ['slice']
1437         else:
1438             return []
1439
1440     def queryset(self, request):
1441         return Reservation.select_by_user(request.user)
1442
1443 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1444     list_display = ("backend_status_icon", "name", )
1445     list_display_links = ('backend_status_icon', 'name', )
1446     user_readonly_fields = ['name']
1447     user_readonly_inlines = []
1448
1449 class RouterAdmin(PlanetStackBaseAdmin):
1450     list_display = ("backend_status_icon", "name", )
1451     list_display_links = ('backend_status_icon', 'name', )
1452     user_readonly_fields = ['name']
1453     user_readonly_inlines = []
1454
1455 class RouterInline(PlStackTabularInline):
1456     model = Router.networks.through
1457     extra = 0
1458     verbose_name_plural = "Routers"
1459     verbose_name = "Router"
1460     suit_classes = 'suit-tab suit-tab-routers'
1461
1462 class NetworkParameterInline(PlStackGenericTabularInline):
1463     model = NetworkParameter
1464     extra = 0
1465     verbose_name_plural = "Parameters"
1466     verbose_name = "Parameter"
1467     suit_classes = 'suit-tab suit-tab-netparams'
1468     fields = ['backend_status_icon', 'parameter', 'value']
1469     readonly_fields = ('backend_status_icon', )
1470
1471 class NetworkSliversInline(PlStackTabularInline):
1472     fields = ['backend_status_icon', 'network','sliver','ip']
1473     readonly_fields = ("backend_status_icon", "ip", )
1474     model = NetworkSliver
1475     selflink_fieldname = "sliver"
1476     extra = 0
1477     verbose_name_plural = "Slivers"
1478     verbose_name = "Sliver"
1479     suit_classes = 'suit-tab suit-tab-networkslivers'
1480
1481 class NetworkSlicesInline(PlStackTabularInline):
1482     model = NetworkSlice
1483     selflink_fieldname = "slice"
1484     extra = 0
1485     verbose_name_plural = "Slices"
1486     verbose_name = "Slice"
1487     suit_classes = 'suit-tab suit-tab-networkslices'
1488     fields = ['backend_status_icon', 'network','slice']
1489     readonly_fields = ('backend_status_icon', )
1490
1491 class ControllerNetworkInline(PlStackTabularInline):
1492     model = ControllerNetwork
1493     extra = 0
1494     verbose_name_plural = "Controller Networks"
1495     verbose_name = "Controller Network"
1496     suit_classes = 'suit-tab suit-tab-admin-only'
1497     fields = ['backend_status_icon', 'controller','net_id','subnet_id']
1498     readonly_fields = ('backend_status_icon', )
1499
1500 class NetworkForm(forms.ModelForm):
1501     class Meta:
1502         model = Network
1503         widgets = {
1504             'topologyParameters': UploadTextareaWidget,
1505             'controllerParameters': UploadTextareaWidget,
1506         }
1507
1508 class NetworkAdmin(PlanetStackBaseAdmin):
1509     list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1510     list_display_links = ('backend_status_icon', 'name', )
1511     readonly_fields = ("subnet", )
1512
1513     inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1514     admin_inlines = [ControllerNetworkInline]
1515
1516     form=NetworkForm
1517
1518     fieldsets = [
1519         (None, {'fields': ['backend_status_text', 'name','template','ports','labels','owner','guaranteed_bandwidth', 'permit_all_slices','permitted_slices','network_id','router_id','subnet_id','subnet'],
1520                 'classes':['suit-tab suit-tab-general']}),
1521         (None, {'fields': ['topology_parameters', 'controller_url', 'controller_parameters'],
1522                 'classes':['suit-tab suit-tab-sdn']}),
1523                 ]
1524
1525     readonly_fields = ('backend_status_text', )
1526     user_readonly_fields = ['name','template','ports','labels','owner','guaranteed_bandwidth', 'permit_all_slices','permitted_slices','network_id','router_id','subnet_id','subnet']
1527
1528     @property
1529     def suit_form_tabs(self):
1530         tabs=[('general','Network Details'),
1531             ('sdn', 'SDN Configuration'),
1532             ('netparams', 'Parameters'),
1533             ('networkslivers','Slivers'),
1534             ('networkslices','Slices'),
1535             ('routers','Routers'),
1536         ]
1537
1538         request=getattr(_thread_locals, "request", None)
1539         if request and request.user.is_admin:
1540             tabs.append( ('admin-only', 'Admin-Only') )
1541
1542         return tabs
1543
1544
1545 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1546     list_display = ("backend_status_icon", "name", "guaranteed_bandwidth", "visibility")
1547     list_display_links = ('backend_status_icon', 'name', )
1548     user_readonly_fields = ["name", "guaranteed_bandwidth", "visibility"]
1549     user_readonly_inlines = []
1550     fieldsets = [
1551         (None, {'fields': ['name', 'description', 'guaranteed_bandwidth', 'visibility', 'translation', 'shared_network_name', 'shared_network_id', 'topology_kind', 'controller_kind'],
1552                 'classes':['suit-tab suit-tab-general']}),]
1553     suit_form_tabs = (('general','Network Template Details'), )
1554
1555 class FlavorAdmin(PlanetStackBaseAdmin):
1556     list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1557     list_display_links = ("backend_status_icon", "name")
1558     user_readonly_fields = ("name", "flavor")
1559     fields = ("name", "description", "flavor", "order", "default")
1560
1561 # register a signal that caches the user's credentials when they log in
1562 def cache_credentials(sender, user, request, **kwds):
1563     auth = {'username': request.POST['username'],
1564             'password': request.POST['password']}
1565     request.session['auth'] = auth
1566 user_logged_in.connect(cache_credentials)
1567
1568 def dollar_field(fieldName, short_description):
1569     def newFunc(self, obj):
1570         try:
1571             x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1572         except:
1573             x=getattr(obj, fieldName, 0.0)
1574         return x
1575     newFunc.short_description = short_description
1576     return newFunc
1577
1578 def right_dollar_field(fieldName, short_description):
1579     def newFunc(self, obj):
1580         try:
1581             #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1582             x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1583         except:
1584             x=getattr(obj, fieldName, 0.0)
1585         return x
1586     newFunc.short_description = short_description
1587     newFunc.allow_tags = True
1588     return newFunc
1589
1590 class InvoiceChargeInline(PlStackTabularInline):
1591     model = Charge
1592     extra = 0
1593     verbose_name_plural = "Charges"
1594     verbose_name = "Charge"
1595     exclude = ['account']
1596     fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1597     readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1598     can_delete = False
1599     max_num = 0
1600
1601     dollar_amount = right_dollar_field("amount", "Amount")
1602
1603 class InvoiceAdmin(admin.ModelAdmin):
1604     list_display = ("date", "account")
1605
1606     inlines = [InvoiceChargeInline]
1607
1608     fields = ["date", "account", "dollar_amount"]
1609     readonly_fields = ["date", "account", "dollar_amount"]
1610
1611     dollar_amount = dollar_field("amount", "Amount")
1612
1613 class InvoiceInline(PlStackTabularInline):
1614     model = Invoice
1615     extra = 0
1616     verbose_name_plural = "Invoices"
1617     verbose_name = "Invoice"
1618     fields = ["date", "dollar_amount"]
1619     readonly_fields = ["date", "dollar_amount"]
1620     suit_classes = 'suit-tab suit-tab-accountinvoice'
1621     can_delete=False
1622     max_num=0
1623
1624     dollar_amount = right_dollar_field("amount", "Amount")
1625
1626 class PendingChargeInline(PlStackTabularInline):
1627     model = Charge
1628     extra = 0
1629     verbose_name_plural = "Charges"
1630     verbose_name = "Charge"
1631     exclude = ["invoice"]
1632     fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1633     readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1634     suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1635     can_delete=False
1636     max_num=0
1637
1638     def queryset(self, request):
1639         qs = super(PendingChargeInline, self).queryset(request)
1640         qs = qs.filter(state="pending")
1641         return qs
1642
1643     dollar_amount = right_dollar_field("amount", "Amount")
1644
1645 class PaymentInline(PlStackTabularInline):
1646     model=Payment
1647     extra = 1
1648     verbose_name_plural = "Payments"
1649     verbose_name = "Payment"
1650     fields = ["date", "dollar_amount"]
1651     readonly_fields = ["date", "dollar_amount"]
1652     suit_classes = 'suit-tab suit-tab-accountpayments'
1653     can_delete=False
1654     max_num=0
1655
1656     dollar_amount = right_dollar_field("amount", "Amount")
1657
1658 class AccountAdmin(admin.ModelAdmin):
1659     list_display = ("site", "balance_due")
1660
1661     inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1662
1663     fieldsets = [
1664         (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1665
1666     readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1667
1668     suit_form_tabs =(
1669         ('general','Account Details'),
1670         ('accountinvoice', 'Invoices'),
1671         ('accountpayments', 'Payments'),
1672         ('accountpendingcharges','Pending Charges'),
1673     )
1674
1675     dollar_balance_due = dollar_field("balance_due", "Balance Due")
1676     dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1677     dollar_total_payments = dollar_field("total_payments", "Total Payments")
1678
1679 # Now register the new UserAdmin...
1680 admin.site.register(User, UserAdmin)
1681 # ... and, since we're not using Django's builtin permissions,
1682 # unregister the Group model from admin.
1683 #admin.site.unregister(Group)
1684
1685 #Do not show django evolution in the admin interface
1686 from django_evolution.models import Version, Evolution
1687 #admin.site.unregister(Version)
1688 #admin.site.unregister(Evolution)
1689
1690
1691 # When debugging it is often easier to see all the classes, but for regular use 
1692 # only the top-levels should be displayed
1693 showAll = False
1694
1695 admin.site.register(Deployment, DeploymentAdmin)
1696 admin.site.register(Controller, ControllerAdmin)
1697 admin.site.register(Site, SiteAdmin)
1698 admin.site.register(Slice, SliceAdmin)
1699 admin.site.register(Service, ServiceAdmin)
1700 admin.site.register(Reservation, ReservationAdmin)
1701 admin.site.register(Network, NetworkAdmin)
1702 admin.site.register(Router, RouterAdmin)
1703 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1704 admin.site.register(Account, AccountAdmin)
1705 admin.site.register(Invoice, InvoiceAdmin)
1706
1707 if True:
1708     admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1709     admin.site.register(ServiceClass, ServiceClassAdmin)
1710     #admin.site.register(PlanetStack)
1711     admin.site.register(Tag, TagAdmin)
1712     admin.site.register(ControllerRole)
1713     admin.site.register(SiteRole)
1714     admin.site.register(SliceRole)
1715     admin.site.register(PlanetStackRole)
1716     admin.site.register(Node, NodeAdmin)
1717     #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1718     #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1719     admin.site.register(Sliver, SliverAdmin)
1720     admin.site.register(Image, ImageAdmin)
1721     admin.site.register(DashboardView, DashboardViewAdmin)
1722     admin.site.register(Flavor, FlavorAdmin)
1723