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