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