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