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