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