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