prevent backend-status icon from disappearing
[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', '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 class SliceAdmin(PlanetStackBaseAdmin):
704     form = SliceForm
705     fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
706     fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
707     readonly_fields = ('backend_status_text', )
708     list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_slivers')
709     list_display_links = ('backend_status_icon', 'name', )
710     inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
711
712     user_readonly_fields = fieldList
713
714     suit_form_tabs =(('general', 'Slice Details'),
715         ('slicenetworks','Networks'),
716         ('sliceprivileges','Privileges'),
717         ('slivers','Slivers'),
718         ('tags','Tags'),
719         ('reservations','Reservations'),
720     )
721
722     def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
723         deployment_nodes = []
724         for node in Node.objects.all():
725             deployment_nodes.append( (node.deployment.id, node.id, node.name) )
726
727         deployment_flavors = []
728         for flavor in Flavor.objects.all():
729             for deployment in flavor.deployments.all():
730                 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
731
732         deployment_images = []
733         for image in Image.objects.all():
734             for imageDeployment in image.imagedeployments_set.all():
735                 deployment_images.append( (imageDeployment.deployment.id, image.id, image.name) )
736
737         site_login_bases = []
738         for site in Site.objects.all():
739             site_login_bases.append((site.id, site.login_base))
740
741         context["deployment_nodes"] = deployment_nodes
742         context["deployment_flavors"] = deployment_flavors
743         context["deployment_images"] = deployment_images
744         context["site_login_bases"] = site_login_bases
745         return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
746
747     def formfield_for_foreignkey(self, db_field, request, **kwargs):
748         if db_field.name == 'site':
749             kwargs['queryset'] = Site.select_by_user(request.user)
750             kwargs['widget'] = forms.Select(attrs={'onChange': "update_slice_prefix(this, $($(this).closest('fieldset')[0]).find('.field-name input')[0].id)"})
751
752         return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
753
754     def queryset(self, request):
755         # admins can see all keys. Users can only see slices they belong to.
756         return Slice.select_by_user(request.user)
757
758     def get_formsets(self, request, obj=None):
759         for inline in self.get_inline_instances(request, obj):
760             # hide MyInline in the add view
761             if obj is None:
762                 continue
763             if isinstance(inline, SliverInline):
764                 inline.model.caller = request.user
765             yield inline.get_formset(request, obj)
766
767
768 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
769     fieldsets = [
770         (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
771     ]
772     readonly_fields = ('backend_status_text', )
773     list_display = ('backend_status_icon', 'user', 'slice', 'role')
774     list_display_links = list_display
775
776     user_readonly_fields = ['user', 'slice', 'role']
777     user_readonly_inlines = []
778
779     def formfield_for_foreignkey(self, db_field, request, **kwargs):
780         if db_field.name == 'slice':
781             kwargs['queryset'] = Slice.select_by_user(request.user)
782         
783         if db_field.name == 'user':
784             kwargs['queryset'] = User.select_by_user(request.user)
785
786         return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
787
788     def queryset(self, request):
789         # admins can see all memberships. Users can only see memberships of
790         # slices where they have the admin role.
791         return SlicePrivilege.select_by_user(request.user)
792
793     def save_model(self, request, obj, form, change):
794         # update openstack connection to use this site/tenant
795         auth = request.session.get('auth', {})
796         auth['tenant'] = obj.slice.slicename
797         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
798         obj.save()
799
800     def delete_model(self, request, obj):
801         # update openstack connection to use this site/tenant
802         auth = request.session.get('auth', {})
803         auth['tenant'] = obj.slice.slicename
804         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
805         obj.delete()
806
807
808 class ImageAdmin(PlanetStackBaseAdmin):
809
810     fieldsets = [('Image Details',
811                    {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
812                     'classes': ['suit-tab suit-tab-general']})
813                ]
814     readonly_fields = ('backend_status_text', )
815
816     suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
817
818     inlines = [SliverInline, ImageDeploymentsInline]
819
820     user_readonly_fields = ['name', 'disk_format', 'container_format']
821
822     list_display = ['backend_status_icon', 'name']
823     list_display_links = ('backend_status_icon', 'name', )
824
825 class NodeForm(forms.ModelForm):
826     class Meta:
827         widgets = {
828             'site': LinkedSelect,
829             'deployment': LinkedSelect
830         }
831
832 class NodeAdmin(PlanetStackBaseAdmin):
833     form = NodeForm
834     list_display = ('backend_status_icon', 'name', 'site', 'deployment')
835     list_display_links = ('backend_status_icon', 'name', )
836     list_filter = ('deployment',)
837
838     inlines = [TagInline,SliverInline]
839     fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
840     readonly_fields = ('backend_status_text', )
841
842     user_readonly_fields = ['name','site','deployment']
843     user_readonly_inlines = [TagInline,SliverInline]
844
845     suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
846
847
848 class SliverForm(forms.ModelForm):
849     class Meta:
850         model = Sliver
851         ip = forms.CharField(widget=PlainTextWidget)
852         instance_name = forms.CharField(widget=PlainTextWidget)
853         widgets = {
854             'ip': PlainTextWidget(),
855             'instance_name': PlainTextWidget(),
856             'slice': LinkedSelect,
857             'deploymentNetwork': LinkedSelect,
858             'node': LinkedSelect,
859             'image': LinkedSelect
860         }
861
862 class TagAdmin(PlanetStackBaseAdmin):
863     list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
864     list_display_links = list_display
865     user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
866     user_readonly_inlines = []
867
868 class SliverAdmin(PlanetStackBaseAdmin):
869     form = SliverForm
870     fieldsets = [
871         ('Sliver Details', {'fields': ['backend_status_text', 'slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
872     ]
873     readonly_fields = ('backend_status_text', )
874     list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'flavor', 'image', 'node', 'deploymentNetwork']
875     list_display_links = ('backend_status_icon', 'ip',)
876
877     suit_form_tabs =(('general', 'Sliver Details'),
878         ('tags','Tags'),
879     )
880
881     inlines = [TagInline]
882
883     user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image']
884
885     def formfield_for_foreignkey(self, db_field, request, **kwargs):
886         if db_field.name == 'slice':
887             kwargs['queryset'] = Slice.select_by_user(request.user)
888
889         return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
890
891     def queryset(self, request):
892         # admins can see all slivers. Users can only see slivers of
893         # the slices they belong to.
894         return Sliver.select_by_user(request.user)
895
896
897     def get_formsets(self, request, obj=None):
898         # make some fields read only if we are updating an existing record
899         if obj == None:
900             #self.readonly_fields = ('ip', 'instance_name')
901             self.readonly_fields = ('backend_status_text')
902         else:
903             self.readonly_fields = ('backend_status_text')
904             #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
905
906         for inline in self.get_inline_instances(request, obj):
907             # hide MyInline in the add view
908             if obj is None:
909                 continue
910             if isinstance(inline, SliverInline):
911                 inline.model.caller = request.user
912             yield inline.get_formset(request, obj)
913
914     #def save_model(self, request, obj, form, change):
915     #    # update openstack connection to use this site/tenant
916     #    auth = request.session.get('auth', {})
917     #    auth['tenant'] = obj.slice.name
918     #    obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
919     #    obj.creator = request.user
920     #    obj.save()
921
922     #def delete_model(self, request, obj):
923     #    # update openstack connection to use this site/tenant
924     #    auth = request.session.get('auth', {})
925     #    auth['tenant'] = obj.slice.name
926     #    obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
927     #    obj.delete()
928
929 class UserCreationForm(forms.ModelForm):
930     """A form for creating new users. Includes all the required
931     fields, plus a repeated password."""
932     password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
933     password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
934
935     class Meta:
936         model = User
937         fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
938
939     def clean_password2(self):
940         # Check that the two password entries match
941         password1 = self.cleaned_data.get("password1")
942         password2 = self.cleaned_data.get("password2")
943         if password1 and password2 and password1 != password2:
944             raise forms.ValidationError("Passwords don't match")
945         return password2
946
947     def save(self, commit=True):
948         # Save the provided password in hashed format
949         user = super(UserCreationForm, self).save(commit=False)
950         user.password = self.cleaned_data["password1"]
951         #user.set_password(self.cleaned_data["password1"])
952         if commit:
953             user.save()
954         return user
955
956
957 class UserChangeForm(forms.ModelForm):
958     """A form for updating users. Includes all the fields on
959     the user, but replaces the password field with admin's
960     password hash display field.
961     """
962     password = ReadOnlyPasswordHashField(label='Password',
963                    help_text= '<a href=\"password/\">Change Password</a>.')
964
965     class Meta:
966         model = User
967
968     def clean_password(self):
969         # Regardless of what the user provides, return the initial value.
970         # This is done here, rather than on the field, because the
971         # field does not have access to the initial value
972         return self.initial["password"]
973
974 class UserDashboardViewInline(PlStackTabularInline):
975     model = UserDashboardView
976     extra = 0
977     suit_classes = 'suit-tab suit-tab-dashboards'
978     fields = ['user', 'dashboardView', 'order']
979
980 class UserAdmin(UserAdmin):
981     class Meta:
982         app_label = "core"
983
984     # The forms to add and change user instances
985     form = UserChangeForm
986     add_form = UserCreationForm
987
988     # The fields to be used in displaying the User model.
989     # These override the definitions on the base UserAdmin
990     # that reference specific fields on auth.User.
991     list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
992     list_filter = ('site',)
993     inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
994
995     fieldListLoginDetails = ['email','site','password','is_active','is_readonly','is_admin','public_key']
996     fieldListContactInfo = ['firstname','lastname','phone','timezone']
997
998     fieldsets = (
999         ('Login Details', {'fields': ['backend_status_text', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1000         ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1001         #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1002         #('Important dates', {'fields': ('last_login',)}),
1003     )
1004     add_fieldsets = (
1005         (None, {
1006             'classes': ('wide',),
1007             'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
1008         ),
1009     )
1010     readonly_fields = ('backend_status_text', )
1011     search_fields = ('email',)
1012     ordering = ('email',)
1013     filter_horizontal = ()
1014
1015     user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1016
1017     suit_form_tabs =(('general','Login Details'),
1018                      ('contact','Contact Information'),
1019                      ('sliceprivileges','Slice Privileges'),
1020                      ('siteprivileges','Site Privileges'),
1021                      ('deploymentprivileges','Deployment Privileges'),
1022                      ('dashboards','Dashboard Views'))
1023
1024     def formfield_for_foreignkey(self, db_field, request, **kwargs):
1025         if db_field.name == 'site':
1026             kwargs['queryset'] = Site.select_by_user(request.user)
1027
1028         return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1029
1030     def has_add_permission(self, request, obj=None):
1031         return (not self.__user_is_readonly(request))
1032
1033     def has_delete_permission(self, request, obj=None):
1034         return (not self.__user_is_readonly(request))
1035
1036     def get_actions(self,request):
1037         actions = super(UserAdmin,self).get_actions(request)
1038
1039         if self.__user_is_readonly(request):
1040             if 'delete_selected' in actions:
1041                 del actions['delete_selected']
1042
1043         return actions
1044
1045     def change_view(self,request,object_id, extra_context=None):
1046
1047         if self.__user_is_readonly(request):
1048             if not hasattr(self, "readonly_save"):
1049                 # save the original readonly fields\r
1050                 self.readonly_save = self.readonly_fields\r
1051                 self.inlines_save = self.inlines
1052             if hasattr(self, "user_readonly_fields"):
1053                 self.readonly_fields=self.user_readonly_fields
1054             if hasattr(self, "user_readonly_inlines"):
1055                 self.inlines = self.user_readonly_inlines
1056         else:
1057             if hasattr(self, "readonly_save"):\r
1058                 # restore the original readonly fields\r
1059                 self.readonly_fields = self.readonly_save\r
1060                 self.inlines = self.inlines_save
1061
1062         try:
1063             return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1064         except PermissionDenied:
1065             pass
1066         if request.method == 'POST':
1067             raise PermissionDenied
1068         request.readonly = True
1069         return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1070
1071     def __user_is_readonly(self, request):
1072         #groups = [x.name for x in request.user.groups.all() ]
1073         #return "readonly" in groups
1074         return request.user.isReadOnlyUser()
1075
1076     def queryset(self, request):
1077         return User.select_by_user(request.user)
1078
1079     def backend_status_text(self, obj):
1080         return mark_safe(backend_text(obj))
1081
1082     def backend_status_icon(self, obj):
1083         return mark_safe(backend_icon(obj))
1084     backend_status_icon.short_description = ""
1085
1086 class DashboardViewAdmin(PlanetStackBaseAdmin):
1087     fieldsets = [('Dashboard View Details',
1088                    {'fields': ['backend_status_text', 'name', 'url'],
1089                     'classes': ['suit-tab suit-tab-general']})
1090                ]
1091     readonly_fields = ('backend_status_text', )
1092
1093     suit_form_tabs =(('general','Dashboard View Details'),)
1094
1095 class ServiceResourceInline(PlStackTabularInline):
1096     model = ServiceResource
1097     extra = 0
1098
1099 class ServiceClassAdmin(PlanetStackBaseAdmin):
1100     list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1101     list_display_links = ('backend_status_icon', 'name', )
1102     inlines = [ServiceResourceInline]
1103
1104     user_readonly_fields = ['name', 'commitment', 'membershipFee']
1105     user_readonly_inlines = []
1106
1107 class ReservedResourceInline(PlStackTabularInline):
1108     model = ReservedResource
1109     extra = 0
1110     suit_classes = 'suit-tab suit-tab-reservedresources'
1111
1112     def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1113         field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1114
1115         if db_field.name == 'resource':
1116             # restrict resources to those that the slice's service class allows
1117             if request._slice is not None:
1118                 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1119                 if len(field.queryset) > 0:
1120                     field.initial = field.queryset.all()[0]
1121             else:\r
1122                 field.queryset = field.queryset.none()\r
1123         elif db_field.name == 'sliver':\r
1124             # restrict slivers to those that belong to the slice\r
1125             if request._slice is not None:\r
1126                 field.queryset = field.queryset.filter(slice = request._slice)
1127             else:
1128                 field.queryset = field.queryset.none()\r
1129 \r
1130         return field
1131
1132     def queryset(self, request):
1133         return ReservedResource.select_by_user(request.user)
1134
1135 class ReservationChangeForm(forms.ModelForm):
1136     class Meta:
1137         model = Reservation
1138         widgets = {
1139             'slice' : LinkedSelect
1140         }
1141
1142 class ReservationAddForm(forms.ModelForm):
1143     slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1144     refresh = forms.CharField(widget=forms.HiddenInput())
1145
1146     class Media:
1147        css = {'all': ('planetstack.css',)}   # .field-refresh { display: none; }
1148
1149     def clean_slice(self):
1150         slice = self.cleaned_data.get("slice")
1151         x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1152         if len(x) == 0:
1153             raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1154         return slice
1155
1156     class Meta:
1157         model = Reservation
1158         widgets = {
1159             'slice' : LinkedSelect
1160         }
1161
1162
1163 class ReservationAddRefreshForm(ReservationAddForm):
1164     """ This form is displayed when the Reservation Form receives an update
1165         from the Slice dropdown onChange handler. It doesn't validate the
1166         data and doesn't save the data. This will cause the form to be
1167         redrawn.
1168     """
1169
1170     """ don't validate anything other than slice """
1171     dont_validate_fields = ("startTime", "duration")
1172
1173     def full_clean(self):
1174         result = super(ReservationAddForm, self).full_clean()
1175
1176         for fieldname in self.dont_validate_fields:
1177             if fieldname in self._errors:
1178                 del self._errors[fieldname]
1179
1180         return result
1181
1182     """ don't save anything """
1183     def is_valid(self):
1184         return False
1185
1186 class ReservationAdmin(PlanetStackBaseAdmin):
1187     fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
1188     fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1189     readonly_fields = ('backend_status_text', )
1190     list_display = ('startTime', 'duration')
1191     form = ReservationAddForm
1192
1193     suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1194
1195     inlines = [ReservedResourceInline]
1196     user_readonly_fields = fieldList
1197
1198     def add_view(self, request, form_url='', extra_context=None):
1199         timezone.activate(request.user.timezone)
1200         request._refresh = False
1201         request._slice = None
1202         if request.method == 'POST':
1203             # "refresh" will be set to "1" if the form was submitted due to
1204             # a change in the Slice dropdown.
1205             if request.POST.get("refresh","1") == "1":
1206                 request._refresh = True
1207                 request.POST["refresh"] = "0"
1208
1209             # Keep track of the slice that was selected, so the
1210             # reservedResource inline can filter items for the slice.
1211             request._slice = request.POST.get("slice",None)
1212             if (request._slice is not None):
1213                 request._slice = Slice.objects.get(id=request._slice)
1214
1215         result =  super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1216         return result
1217
1218     def changelist_view(self, request, extra_context = None):
1219         timezone.activate(request.user.timezone)
1220         return super(ReservationAdmin, self).changelist_view(request, extra_context)
1221
1222     def get_form(self, request, obj=None, **kwargs):
1223         request._obj_ = obj
1224         if obj is not None:
1225             # For changes, set request._slice to the slice already set in the
1226             # object.
1227             request._slice = obj.slice
1228             self.form = ReservationChangeForm
1229         else:
1230             if getattr(request, "_refresh", False):
1231                 self.form = ReservationAddRefreshForm
1232             else:
1233                 self.form = ReservationAddForm
1234         return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1235
1236     def get_readonly_fields(self, request, obj=None):
1237         if (obj is not None):
1238             # Prevent slice from being changed after the reservation has been
1239             # created.
1240             return ['slice']
1241         else:
1242             return []
1243
1244     def queryset(self, request):
1245         return Reservation.select_by_user(request.user)
1246
1247 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1248     list_display = ("backend_status_icon", "name", )
1249     list_display_links = ('backend_status_icon', 'name', )
1250     user_readonly_fields = ['name']
1251     user_readonly_inlines = []
1252
1253 class RouterAdmin(PlanetStackBaseAdmin):
1254     list_display = ("backend_status_icon", "name", )
1255     list_display_links = ('backend_status_icon', 'name', )
1256     user_readonly_fields = ['name']
1257     user_readonly_inlines = []
1258
1259 class RouterInline(PlStackTabularInline):
1260     model = Router.networks.through
1261     extra = 0
1262     verbose_name_plural = "Routers"
1263     verbose_name = "Router"
1264     suit_classes = 'suit-tab suit-tab-routers'
1265
1266 class NetworkParameterInline(PlStackGenericTabularInline):
1267     model = NetworkParameter
1268     extra = 0
1269     verbose_name_plural = "Parameters"
1270     verbose_name = "Parameter"
1271     suit_classes = 'suit-tab suit-tab-netparams'
1272     fields = ['backend_status_icon', 'parameter', 'value']
1273     readonly_fields = ('backend_status_icon', )
1274
1275 class NetworkSliversInline(PlStackTabularInline):
1276     fields = ['backend_status_icon', 'network','sliver','ip']
1277     readonly_fields = ("backend_status_icon", "ip", )
1278     model = NetworkSliver
1279     selflink_fieldname = "sliver"
1280     extra = 0
1281     verbose_name_plural = "Slivers"
1282     verbose_name = "Sliver"
1283     suit_classes = 'suit-tab suit-tab-networkslivers'
1284
1285 class NetworkSlicesInline(PlStackTabularInline):
1286     model = NetworkSlice
1287     selflink_fieldname = "slice"
1288     extra = 0
1289     verbose_name_plural = "Slices"
1290     verbose_name = "Slice"
1291     suit_classes = 'suit-tab suit-tab-networkslices'
1292     fields = ['backend_status_icon', 'network','slice']
1293     readonly_fields = ('backend_status_icon', )
1294
1295 class NetworkAdmin(PlanetStackBaseAdmin):
1296     list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1297     list_display_links = ('backend_status_icon', 'name', )
1298     readonly_fields = ("subnet", )
1299
1300     inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1301
1302     fieldsets = [
1303         (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']}),]
1304
1305     readonly_fields = ('backend_status_text', )
1306     user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1307
1308     suit_form_tabs =(
1309         ('general','Network Details'),
1310         ('netparams', 'Parameters'),
1311         ('networkslivers','Slivers'),
1312         ('networkslices','Slices'),
1313         ('routers','Routers'),
1314     )
1315 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1316     list_display = ("backend_status_icon", "name", "guaranteedBandwidth", "visibility")
1317     list_display_links = ('backend_status_icon', 'name', )
1318     user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1319     user_readonly_inlines = []
1320
1321 class FlavorAdmin(PlanetStackBaseAdmin):
1322     list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1323     list_display_links = ("backend_status_icon", "name")
1324     user_readonly_fields = ("name", "flavor")
1325     fields = ("name", "description", "flavor", "order", "default")
1326
1327 # register a signal that caches the user's credentials when they log in
1328 def cache_credentials(sender, user, request, **kwds):
1329     auth = {'username': request.POST['username'],
1330             'password': request.POST['password']}
1331     request.session['auth'] = auth
1332 user_logged_in.connect(cache_credentials)
1333
1334 def dollar_field(fieldName, short_description):
1335     def newFunc(self, obj):
1336         try:
1337             x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1338         except:
1339             x=getattr(obj, fieldName, 0.0)
1340         return x
1341     newFunc.short_description = short_description
1342     return newFunc
1343
1344 def right_dollar_field(fieldName, short_description):
1345     def newFunc(self, obj):
1346         try:
1347             #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1348             x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1349         except:
1350             x=getattr(obj, fieldName, 0.0)
1351         return x
1352     newFunc.short_description = short_description
1353     newFunc.allow_tags = True
1354     return newFunc
1355
1356 class InvoiceChargeInline(PlStackTabularInline):
1357     model = Charge
1358     extra = 0
1359     verbose_name_plural = "Charges"
1360     verbose_name = "Charge"
1361     exclude = ['account']
1362     fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1363     readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1364     can_delete = False
1365     max_num = 0
1366
1367     dollar_amount = right_dollar_field("amount", "Amount")
1368
1369 class InvoiceAdmin(admin.ModelAdmin):
1370     list_display = ("date", "account")
1371
1372     inlines = [InvoiceChargeInline]
1373
1374     fields = ["date", "account", "dollar_amount"]
1375     readonly_fields = ["date", "account", "dollar_amount"]
1376
1377     dollar_amount = dollar_field("amount", "Amount")
1378
1379 class InvoiceInline(PlStackTabularInline):
1380     model = Invoice
1381     extra = 0
1382     verbose_name_plural = "Invoices"
1383     verbose_name = "Invoice"
1384     fields = ["date", "dollar_amount"]
1385     readonly_fields = ["date", "dollar_amount"]
1386     suit_classes = 'suit-tab suit-tab-accountinvoice'
1387     can_delete=False
1388     max_num=0
1389
1390     dollar_amount = right_dollar_field("amount", "Amount")
1391
1392 class PendingChargeInline(PlStackTabularInline):
1393     model = Charge
1394     extra = 0
1395     verbose_name_plural = "Charges"
1396     verbose_name = "Charge"
1397     exclude = ["invoice"]
1398     fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1399     readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1400     suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1401     can_delete=False
1402     max_num=0
1403
1404     def queryset(self, request):
1405         qs = super(PendingChargeInline, self).queryset(request)
1406         qs = qs.filter(state="pending")
1407         return qs
1408
1409     dollar_amount = right_dollar_field("amount", "Amount")
1410
1411 class PaymentInline(PlStackTabularInline):
1412     model=Payment
1413     extra = 1
1414     verbose_name_plural = "Payments"
1415     verbose_name = "Payment"
1416     fields = ["date", "dollar_amount"]
1417     readonly_fields = ["date", "dollar_amount"]
1418     suit_classes = 'suit-tab suit-tab-accountpayments'
1419     can_delete=False
1420     max_num=0
1421
1422     dollar_amount = right_dollar_field("amount", "Amount")
1423
1424 class AccountAdmin(admin.ModelAdmin):
1425     list_display = ("site", "balance_due")
1426
1427     inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1428
1429     fieldsets = [
1430         (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1431
1432     readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1433
1434     suit_form_tabs =(
1435         ('general','Account Details'),
1436         ('accountinvoice', 'Invoices'),
1437         ('accountpayments', 'Payments'),
1438         ('accountpendingcharges','Pending Charges'),
1439     )
1440
1441     dollar_balance_due = dollar_field("balance_due", "Balance Due")
1442     dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1443     dollar_total_payments = dollar_field("total_payments", "Total Payments")
1444
1445 # Now register the new UserAdmin...
1446 admin.site.register(User, UserAdmin)
1447 # ... and, since we're not using Django's builtin permissions,
1448 # unregister the Group model from admin.
1449 #admin.site.unregister(Group)
1450
1451 #Do not show django evolution in the admin interface
1452 from django_evolution.models import Version, Evolution
1453 #admin.site.unregister(Version)
1454 #admin.site.unregister(Evolution)
1455
1456
1457 # When debugging it is often easier to see all the classes, but for regular use 
1458 # only the top-levels should be displayed
1459 showAll = False
1460
1461 admin.site.register(Deployment, DeploymentAdmin)
1462 admin.site.register(Site, SiteAdmin)
1463 admin.site.register(Slice, SliceAdmin)
1464 admin.site.register(Service, ServiceAdmin)
1465 admin.site.register(Reservation, ReservationAdmin)
1466 admin.site.register(Network, NetworkAdmin)
1467 admin.site.register(Router, RouterAdmin)
1468 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1469 admin.site.register(Account, AccountAdmin)
1470 admin.site.register(Invoice, InvoiceAdmin)
1471
1472 if True:
1473     admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1474     admin.site.register(ServiceClass, ServiceClassAdmin)
1475     #admin.site.register(PlanetStack)
1476     admin.site.register(Tag, TagAdmin)
1477     admin.site.register(DeploymentRole)
1478     admin.site.register(SiteRole)
1479     admin.site.register(SliceRole)
1480     admin.site.register(PlanetStackRole)
1481     admin.site.register(Node, NodeAdmin)
1482     #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1483     #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1484     admin.site.register(Sliver, SliverAdmin)
1485     admin.site.register(Image, ImageAdmin)
1486     admin.site.register(DashboardView, DashboardViewAdmin)
1487     admin.site.register(Flavor, FlavorAdmin)
1488