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