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