merge
[plstackapi.git] / planetstack / core / admin.py
1 from core.models import Site
2 from core.models import *
3 from openstack.manager import OpenStackManager
4
5 from django.contrib import admin
6 from django.contrib.auth.models import Group
7 from django import forms
8 from django.utils.safestring import mark_safe
9 from django.contrib.auth.admin import UserAdmin
10 from django.contrib.admin.widgets import FilteredSelectMultiple
11 from django.contrib.auth.forms import ReadOnlyPasswordHashField
12 from django.contrib.auth.signals import user_logged_in
13 from django.utils import timezone
14 from django.contrib.contenttypes import generic
15 from suit.widgets import LinkedSelect
16 from django.core.exceptions import PermissionDenied
17 from django.core.urlresolvers import reverse, NoReverseMatch
18
19 import django_evolution
20
21 def backend_icon(obj): # backend_status, enacted, updated):
22     #return "%s %s %s" % (str(obj.updated), str(obj.enacted), str(obj.backend_status))
23     if (obj.enacted is not None) and obj.enacted >= obj.updated:
24         return '<img src="/static/admin/img/icon_success.gif">'
25     else:
26         if obj.backend_status == "Provisioning in progress" or obj.backend_status=="":
27             return '<span title="%s"><img src="/static/admin/img/icon_clock.gif"></span>' % obj.backend_status
28         else:
29             return '<span title="%s"><img src="/static/admin/img/icon_error.gif"></span>' % obj.backend_status
30
31 def backend_text(obj):
32     icon = backend_icon(obj)
33     if (obj.enacted is not None) and obj.enacted >= obj.updated:
34         return "%s %s" % (icon, "successfully enacted") # enacted on %s" % str(obj.enacted))
35     else:
36         return "%s %s" % (icon, obj.backend_status)
37
38 class PlainTextWidget(forms.HiddenInput):
39     input_type = 'hidden'
40
41     def render(self, name, value, attrs=None):
42         if value is None:
43             value = ''
44         return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
45
46 class ReadOnlyAwareAdmin(admin.ModelAdmin):
47
48     def has_add_permission(self, request, obj=None):
49         return (not self.__user_is_readonly(request))
50
51     def has_delete_permission(self, request, obj=None):
52         return (not self.__user_is_readonly(request))
53
54     def save_model(self, request, obj, form, change):
55         if self.__user_is_readonly(request):
56             raise PermissionDenied
57             #pass
58         else:
59             return super(ReadOnlyAwareAdmin, self).save_model(request, obj, form, change)
60
61     def get_actions(self,request):
62         actions = super(ReadOnlyAwareAdmin,self).get_actions(request)
63
64         if self.__user_is_readonly(request):
65             if 'delete_selected' in actions:
66                 del actions['delete_selected']
67
68         return actions
69
70     def change_view(self,request,object_id, extra_context=None):
71         if self.__user_is_readonly(request):
72             if not hasattr(self, "readonly_save"):\r
73                 # save the original readonly fields\r
74                 self.readonly_save = self.readonly_fields\r
75                 self.inlines_save = self.inlines\r
76             if hasattr(self, "user_readonly_fields"):\r
77                 self.readonly_fields=self.user_readonly_fields\r
78             if hasattr(self, "user_readonly_inlines"):\r
79                 self.inlines = self.user_readonly_inlines\r
80         else:\r
81             if hasattr(self, "readonly_save"):\r
82                 # restore the original readonly fields\r
83                 self.readonly_fields = self.readonly_save\r
84             if hasattr(self, "inlines_save"):\r
85                 self.inlines = self.inlines_save
86
87         try:
88             return super(ReadOnlyAwareAdmin, self).change_view(request, object_id, extra_context=extra_context)
89         except PermissionDenied:
90             pass
91         if request.method == 'POST':
92             raise PermissionDenied
93         request.readonly = True
94         return super(ReadOnlyAwareAdmin, self).change_view(request, object_id, extra_context=extra_context)
95
96     def __user_is_readonly(self, request):
97         return request.user.isReadOnlyUser()
98
99     def backend_status_text(self, obj):
100         return mark_safe(backend_text(obj))
101
102     def backend_status_icon(self, obj):
103         return mark_safe(backend_icon(obj))
104     backend_status_icon.short_description = ""
105
106
107 class SingletonAdmin (ReadOnlyAwareAdmin):
108     def has_add_permission(self, request):
109         if not super(SingletonAdmin, self).has_add_permission(request):
110             return False
111
112         num_objects = self.model.objects.count()
113         if num_objects >= 1:
114             return False
115         else:
116             return True
117
118
119 class PlStackTabularInline(admin.TabularInline):
120     def __init__(self, *args, **kwargs):
121         super(PlStackTabularInline, self).__init__(*args, **kwargs)
122
123         # InlineModelAdmin as no get_fields() method, so in order to add
124         # the selflink field, we override __init__ to modify self.fields and
125         # self.readonly_fields.
126
127         self.setup_selflink()
128
129     def get_change_url(self, model, id):
130         """ Get the URL to a change form in the admin for this model """
131         reverse_path = "admin:%s_change" % (model._meta.db_table)
132         try:
133             url = reverse(reverse_path, args=(id,))
134         except NoReverseMatch:
135             return None
136
137         return url
138
139     def setup_selflink(self):
140         if hasattr(self, "selflink_fieldname"):
141             """ self.selflink_model can be defined to punch through a relation
142                 to its target object. For example, in SliceNetworkInline, set
143                 selflink_model = "network", and the URL will lead to the Network
144                 object instead of trying to bring up a change view of the
145                 SliceNetwork object.
146             """
147             self.selflink_model = getattr(self.model,self.selflink_fieldname).field.rel.to
148         else:
149             self.selflink_model = self.model
150
151         url = self.get_change_url(self.selflink_model, 0)
152
153         # We don't have an admin for this object, so don't create the
154         # selflink.
155         if (url == None):
156             return
157
158         # Since we need to add "selflink" to the field list, we need to create
159         # self.fields if it is None.
160         if (self.fields is None):
161             self.fields = []
162             for f in self.model._meta.fields:
163                 if f.editable and f.name != "id":
164                     self.fields.append(f.name)
165
166         self.fields = tuple(self.fields) + ("selflink", )
167
168         if self.readonly_fields is None:
169             self.readonly_fields = ()
170
171         self.readonly_fields = tuple(self.readonly_fields) + ("selflink", )
172
173     def selflink(self, obj):
174         if hasattr(self, "selflink_fieldname"):
175             obj = getattr(obj, self.selflink_fieldname)
176
177         if obj.id:
178             url = self.get_change_url(self.selflink_model, obj.id)
179             return "<a href='%s'>Details</a>" % str(url)
180         else:\r
181             return "Not present"\r
182
183     selflink.allow_tags = True
184     selflink.short_description = "Details"
185
186     def has_add_permission(self, request):
187         return not request.user.isReadOnlyUser()
188
189     def get_readonly_fields(self, request, obj=None):
190         readonly_fields = list(self.readonly_fields)[:]
191         if request.user.isReadOnlyUser():
192             for field in self.fields:
193                 if not field in readonly_fields:
194                     readonly_fields.append(field)
195         return readonly_fields
196
197     def backend_status_icon(self, obj):
198         return mark_safe(backend_icon(obj))
199     backend_status_icon.short_description = ""
200
201 class PlStackGenericTabularInline(generic.GenericTabularInline):
202     def has_add_permission(self, request):
203         return not request.user.isReadOnlyUser()
204
205     def get_readonly_fields(self, request, obj=None):
206         readonly_fields = list(self.readonly_fields)[:]
207         if request.user.isReadOnlyUser():
208             for field in self.fields:
209                 if not field in readonly_fields:
210                     readonly_fields.append(field)
211         return readonly_fields
212
213     def backend_status_icon(self, obj):
214         return mark_safe(backend_icon(obj))
215     backend_status_icon.short_description = ""
216
217 class ReservationInline(PlStackTabularInline):
218     model = Reservation
219     extra = 0
220     suit_classes = 'suit-tab suit-tab-reservations'
221
222     def queryset(self, request):
223         return Reservation.select_by_user(request.user)
224
225 class TagInline(PlStackGenericTabularInline):
226     model = Tag
227     extra = 0
228     suit_classes = 'suit-tab suit-tab-tags'
229     fields = ['service', 'name', 'value']
230
231     def queryset(self, request):
232         return Tag.select_by_user(request.user)
233
234 class NetworkLookerUpper:
235     """ This is a callable that looks up a network name in a sliver and returns
236         the ip address for that network.
237     """
238
239     byNetworkName = {}    # class variable
240
241     def __init__(self, name):
242         self.short_description = name
243         self.__name__ = name
244         self.network_name = name
245
246     def __call__(self, obj):
247         if obj is not None:
248             for nbs in obj.networksliver_set.all():
249                 if (nbs.network.name == self.network_name):
250                     return nbs.ip
251         return ""
252
253     def __str__(self):
254         return self.network_name
255
256     @staticmethod
257     def get(network_name):
258         """ We want to make sure we alwars return the same NetworkLookerUpper
259             because sometimes django will cause them to be instantiated multiple
260             times (and we don't want different ones in form.fields vs
261             SliverInline.readonly_fields).
262         """
263         if network_name not in NetworkLookerUpper.byNetworkName:
264             NetworkLookerUpper.byNetworkName[network_name] = NetworkLookerUpper(network_name)
265         return NetworkLookerUpper.byNetworkName[network_name]
266
267 class SliverInline(PlStackTabularInline):
268     model = Sliver
269     fields = ['backend_status_icon', 'all_ips_string', 'instance_name', 'slice', 'deploymentNetwork', 'flavor', 'image', 'node']
270     extra = 0
271     readonly_fields = ['backend_status_icon', 'all_ips_string', 'instance_name']
272     suit_classes = 'suit-tab suit-tab-slivers'
273
274     def queryset(self, request):
275         return Sliver.select_by_user(request.user)
276
277     def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
278         if db_field.name == 'deploymentNetwork':
279            kwargs['queryset'] = Deployment.select_by_acl(request.user)
280            kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_deployment_changed(this);"})
281
282         field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
283
284         return field
285
286 """
287     SMBAKER: This is the old code that implemented each network type as a
288     separate column in the sliver table.
289
290     def _declared_fieldsets(self):
291         # Return None so django will call get_fieldsets and we can insert our
292         # dynamic fields
293         return None
294
295     def get_readonly_fields(self, request, obj=None):
296         readonly_fields = list(super(SliverInline, self).get_readonly_fields(request, obj))
297
298         # Lookup the networks that are bound to the slivers, and add those
299         # network names to the list of readonly fields.
300
301         for sliver in obj.slivers.all():
302             for nbs in sliver.networksliver_set.all():
303                 if nbs.ip:
304                     network_name = nbs.network.name
305                     if network_name not in [str(x) for x in readonly_fields]:
306                         readonly_fields.append(NetworkLookerUpper.get(network_name))
307
308         return readonly_fields
309
310     def get_fieldsets(self, request, obj=None):
311         form = self.get_formset(request, obj).form
312         # fields = the read/write files + the read-only fields
313         fields = list(self.fields)
314         for fieldName in self.get_readonly_fields(request,obj):
315             if not fieldName in fields:
316                 fields.append(fieldName)
317
318         return [(None, {'fields': fields})]
319 """
320
321 class SiteInline(PlStackTabularInline):
322     model = Site
323     extra = 0
324     suit_classes = 'suit-tab suit-tab-sites'
325
326     def queryset(self, request):
327         return Site.select_by_user(request.user)
328
329 class UserInline(PlStackTabularInline):
330     model = User
331     fields = ['backend_status_icon', 'email', 'firstname', 'lastname']
332     readonly_fields = ('backend_status_icon', )
333     extra = 0
334     suit_classes = 'suit-tab suit-tab-users'
335
336     def queryset(self, request):
337         return User.select_by_user(request.user)
338
339 class SliceInline(PlStackTabularInline):
340     model = Slice
341     fields = ['backend_status_icon', 'name', 'site', 'serviceClass', 'service']
342     readonly_fields = ('backend_status_icon', )
343     extra = 0
344     suit_classes = 'suit-tab suit-tab-slices'
345
346     def queryset(self, request):
347         return Slice.select_by_user(request.user)
348
349 class NodeInline(PlStackTabularInline):
350     model = Node
351     extra = 0
352     suit_classes = 'suit-tab suit-tab-nodes'
353     fields = ['backend_status_icon', 'name','deployment','site']
354     readonly_fields = ('backend_status_icon', )
355
356 class DeploymentPrivilegeInline(PlStackTabularInline):
357     model = DeploymentPrivilege
358     extra = 0
359     suit_classes = 'suit-tab suit-tab-deploymentprivileges'
360     fields = ['backend_status_icon', 'user','role','deployment']
361     readonly_fields = ('backend_status_icon', )
362
363     def queryset(self, request):
364         return DeploymentPrivilege.select_by_user(request.user)
365
366 class SitePrivilegeInline(PlStackTabularInline):
367     model = SitePrivilege
368     extra = 0
369     suit_classes = 'suit-tab suit-tab-siteprivileges'
370     fields = ['backend_status_icon', 'user','site', 'role']
371     readonly_fields = ('backend_status_icon', )
372
373     def formfield_for_foreignkey(self, db_field, request, **kwargs):
374         if db_field.name == 'site':
375             kwargs['queryset'] = Site.select_by_user(request.user)
376
377         if db_field.name == 'user':
378             kwargs['queryset'] = User.select_by_user(request.user)
379         return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
380
381     def queryset(self, request):
382         return SitePrivilege.select_by_user(request.user)
383
384 class SiteDeploymentInline(PlStackTabularInline):
385     model = SiteDeployments
386     extra = 0
387     suit_classes = 'suit-tab suit-tab-deployments'
388     fields = ['backend_status_icon', 'deployment','site']
389     readonly_fields = ('backend_status_icon', )
390
391     def formfield_for_foreignkey(self, db_field, request, **kwargs):
392         if db_field.name == 'site':
393             kwargs['queryset'] = Site.select_by_user(request.user)
394
395         if db_field.name == 'deployment':
396             kwargs['queryset'] = Deployment.select_by_user(request.user)
397         return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
398
399     def queryset(self, request):
400         return SiteDeployments.select_by_user(request.user)
401
402
403 class SlicePrivilegeInline(PlStackTabularInline):
404     model = SlicePrivilege
405     suit_classes = 'suit-tab suit-tab-sliceprivileges'
406     extra = 0
407     fields = ('backend_status_icon', 'user', 'slice', 'role')
408     readonly_fields = ('backend_status_icon', )
409
410     def formfield_for_foreignkey(self, db_field, request, **kwargs):
411         if db_field.name == 'slice':
412            kwargs['queryset'] = Slice.select_by_user(request.user)
413         if db_field.name == 'user':
414            kwargs['queryset'] = User.select_by_user(request.user)
415
416         return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
417
418     def queryset(self, request):
419         return SlicePrivilege.select_by_user(request.user)
420
421 class SliceNetworkInline(PlStackTabularInline):
422     model = Network.slices.through
423     selflink_fieldname = "network"
424     extra = 0
425     verbose_name = "Network Connection"
426     verbose_name_plural = "Network Connections"
427     suit_classes = 'suit-tab suit-tab-slicenetworks'
428     fields = ['backend_status_icon', 'network']
429     readonly_fields = ('backend_status_icon', )
430
431 class ImageDeploymentsInline(PlStackTabularInline):
432     model = ImageDeployments
433     extra = 0
434     verbose_name = "Image Deployments"
435     verbose_name_plural = "Image Deployments"
436     suit_classes = 'suit-tab suit-tab-imagedeployments'
437     fields = ['backend_status_icon', 'image', 'deployment', 'glance_image_id']
438     readonly_fields = ['backend_status_icon', 'glance_image_id']
439
440 class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
441     save_on_top = False
442
443     def save_model(self, request, obj, form, change):
444         obj.caller = request.user
445         # update openstack connection to use this site/tenant
446         obj.save_by_user(request.user)
447
448     def delete_model(self, request, obj):
449         obj.delete_by_user(request.user)
450
451     def save_formset(self, request, form, formset, change):
452         instances = formset.save(commit=False)
453         for instance in instances:
454             instance.save_by_user(request.user)
455         formset.save_m2m()
456
457 class SliceRoleAdmin(PlanetStackBaseAdmin):
458     model = SliceRole
459     pass
460
461 class SiteRoleAdmin(PlanetStackBaseAdmin):
462     model = SiteRole
463     pass
464
465 class DeploymentAdminForm(forms.ModelForm):
466     sites = forms.ModelMultipleChoiceField(
467         queryset=Site.objects.all(),
468         required=False,
469         help_text="Select which sites are allowed to host nodes in this deployment",
470         widget=FilteredSelectMultiple(
471             verbose_name=('Sites'), is_stacked=False
472         )
473     )
474     images = forms.ModelMultipleChoiceField(
475         queryset=Image.objects.all(),
476         required=False,
477         help_text="Select which images should be deployed on this deployment",
478         widget=FilteredSelectMultiple(
479             verbose_name=('Images'), is_stacked=False
480         )
481     )
482     flavors = forms.ModelMultipleChoiceField(
483         queryset=Flavor.objects.all(),
484         required=False,
485         help_text="Select which flavors should be usable on this deployment",
486         widget=FilteredSelectMultiple(
487             verbose_name=('Flavors'), is_stacked=False
488         )
489     )
490     class Meta:
491         model = Deployment
492         many_to_many = ["flavors",]
493
494     def __init__(self, *args, **kwargs):
495       request = kwargs.pop('request', None)
496       super(DeploymentAdminForm, self).__init__(*args, **kwargs)
497
498       self.fields['accessControl'].initial = "allow site " + request.user.site.name
499
500       if self.instance and self.instance.pk:
501         self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments_set.all()]
502         self.fields['images'].initial = [x.image for x in self.instance.imagedeployments_set.all()]
503         self.fields['flavors'].initial = self.instance.flavors.all()
504
505     def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
506         """ helper function for handling m2m relations from the MultipleChoiceField
507
508             this_obj: the source object we want to link from
509
510             selected_objs: a list of destination objects we want to link to
511
512             all_relations: the full set of relations involving this_obj, including ones we don't want
513
514             relation_class: the class that implements the relation from source to dest
515
516             local_attrname: field name representing this_obj in relation_class
517
518             foreign_attrname: field name representing selected_objs in relation_class
519
520             This function will remove all newobjclass relations from this_obj
521             that are not contained in selected_objs, and add any relations that
522             are in selected_objs but don't exist in the data model yet.
523         """
524
525         existing_dest_objs = []
526         for relation in list(all_relations):
527             if getattr(relation, foreign_attrname) not in selected_objs:
528                 #print "deleting site", sdp.site
529                 relation.delete()
530             else:
531                 existing_dest_objs.append(getattr(relation, foreign_attrname))
532
533         for dest_obj in selected_objs:
534             if dest_obj not in existing_dest_objs:
535                 #print "adding site", site
536                 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
537                 relation = relation_class(**kwargs)
538                 relation.save()
539
540     def save(self, commit=True):
541       deployment = super(DeploymentAdminForm, self).save(commit=False)
542
543       deployment.flavors = self.cleaned_data['flavors']
544
545       if commit:
546         deployment.save()
547
548       if deployment.pk:
549         # save_m2m() doesn't seem to work with 'through' relations. So we
550         #    create/destroy the through models ourselves. There has to be
551         #    a better way...
552
553         self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments_set.all(), SiteDeployments, "deployment", "site")
554         self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments_set.all(), ImageDeployments, "deployment", "image")
555
556       self.save_m2m()
557
558       return deployment
559
560 class DeploymentAdminROForm(DeploymentAdminForm):
561     def save(self, commit=True):
562         raise PermissionDenied
563
564 class SiteAssocInline(PlStackTabularInline):
565     model = Site.deployments.through
566     extra = 0
567     suit_classes = 'suit-tab suit-tab-sites'
568
569 class DeploymentAdmin(PlanetStackBaseAdmin):
570     model = Deployment
571     fieldList = ['backend_status_text', 'name', 'sites', 'images', 'flavors', 'accessControl']
572     fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
573     inlines = [DeploymentPrivilegeInline,NodeInline,TagInline] # ,ImageDeploymentsInline]
574     list_display = ['backend_status_icon', 'name']
575     list_display_links = ('backend_status_icon', 'name', )
576     readonly_fields = ('backend_status_text', )
577
578     user_readonly_fields = ['name']
579
580     suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags')) # ,('imagedeployments','Images'))
581
582     def get_form(self, request, obj=None, **kwargs):
583         if request.user.isReadOnlyUser():
584             kwargs["form"] = DeploymentAdminROForm
585         else:
586             kwargs["form"] = DeploymentAdminForm
587         adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
588
589         # from stackexchange: pass the request object into the form
590
591         class AdminFormMetaClass(adminForm):
592            def __new__(cls, *args, **kwargs):
593                kwargs['request'] = request
594                return adminForm(*args, **kwargs)
595
596         return AdminFormMetaClass
597
598 class ServiceAttrAsTabInline(PlStackTabularInline):
599     model = ServiceAttribute
600     fields = ['name','value']
601     extra = 0
602     suit_classes = 'suit-tab suit-tab-serviceattrs'
603
604 class ServiceAdmin(PlanetStackBaseAdmin):
605     list_display = ("backend_status_icon","name","description","versionNumber","enabled","published")
606     list_display_links = ('backend_status_icon', 'name', )
607     fieldList = ["backend_status_text","name","description","versionNumber","enabled","published"]
608     fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
609     inlines = [ServiceAttrAsTabInline,SliceInline]
610     readonly_fields = ('backend_status_text', )
611
612     user_readonly_fields = fieldList
613
614     suit_form_tabs =(('general', 'Service Details'),
615         ('slices','Slices'),
616         ('serviceattrs','Additional Attributes'),
617     )
618
619 class SiteAdmin(PlanetStackBaseAdmin):
620     fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
621     fieldsets = [
622         (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
623         #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
624     ]
625     suit_form_tabs =(('general', 'Site Details'),
626         ('users','Users'),
627         ('siteprivileges','Privileges'),
628         ('deployments','Deployments'),
629         ('slices','Slices'),
630         ('nodes','Nodes'),
631         ('tags','Tags'),
632     )
633     readonly_fields = ['backend_status_text', 'accountLink']
634
635     user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
636
637     list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
638     list_display_links = ('backend_status_icon', 'name', )
639     filter_horizontal = ('deployments',)
640     inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentInline]
641     search_fields = ['name']
642
643     def queryset(self, request):
644         return Site.select_by_user(request.user)
645
646     def get_formsets(self, request, obj=None):
647         for inline in self.get_inline_instances(request, obj):
648             # hide MyInline in the add view
649             if obj is None:
650                 continue
651             if isinstance(inline, SliceInline):
652                 inline.model.caller = request.user
653             yield inline.get_formset(request, obj)
654
655     def get_formsets(self, request, obj=None):
656         for inline in self.get_inline_instances(request, obj):
657             # hide MyInline in the add view
658             if obj is None:
659                 continue
660             if isinstance(inline, SliverInline):
661                 inline.model.caller = request.user
662             yield inline.get_formset(request, obj)
663
664     def accountLink(self, obj):
665         link_obj = obj.accounts.all()
666         if link_obj:
667             reverse_path = "admin:core_account_change"
668             url = reverse(reverse_path, args =(link_obj[0].id,))
669             return "<a href='%s'>%s</a>" % (url, "view billing details")
670         else:
671             return "no billing data for this site"
672     accountLink.allow_tags = True
673     accountLink.short_description = "Billing"
674
675     def save_model(self, request, obj, form, change):
676         # update openstack connection to use this site/tenant
677         obj.save_by_user(request.user) 
678
679     def delete_model(self, request, obj):
680         obj.delete_by_user(request.user)
681         
682
683 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
684     fieldList = ['backend_status_text', 'user', 'site', 'role']
685     fieldsets = [
686         (None, {'fields': fieldList, 'classes':['collapse']})
687     ]
688     readonly_fields = ('backend_status_text', )
689     list_display = ('backend_status_icon', 'user', 'site', 'role')
690     list_display_links = list_display
691     user_readonly_fields = fieldList
692     user_readonly_inlines = []
693
694     def formfield_for_foreignkey(self, db_field, request, **kwargs):
695         if db_field.name == 'site':
696             if not request.user.is_admin:
697                 # only show sites where user is an admin or pi
698                 sites = set()
699                 for site_privilege in SitePrivilege.objects.filer(user=request.user):
700                     if site_privilege.role.role_type in ['admin', 'pi']:
701                         sites.add(site_privilege.site)
702                 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
703
704         if db_field.name == 'user':
705             if not request.user.is_admin:
706                 # only show users from sites where caller has admin or pi role
707                 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
708                 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
709                 sites = [site_privilege.site for site_privilege in site_privileges]
710                 site_privileges = SitePrivilege.objects.filter(site__in=sites)
711                 emails = [site_privilege.user.email for site_privilege in site_privileges]
712                 users = User.objects.filter(email__in=emails)
713                 kwargs['queryset'] = users
714
715         return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
716
717     def queryset(self, request):
718         # admins can see all privileges. Users can only see privileges at sites
719         # where they have the admin role or pi role.
720         qs = super(SitePrivilegeAdmin, self).queryset(request)
721         #if not request.user.is_admin:
722         #    roles = Role.objects.filter(role_type__in=['admin', 'pi'])
723         #    site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
724         #    login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
725         #    sites = Site.objects.filter(login_base__in=login_bases)
726         #    qs = qs.filter(site__in=sites)
727         return qs
728
729 class SliceForm(forms.ModelForm):
730     class Meta:
731         model = Slice
732         widgets = {
733             'service': LinkedSelect
734         }
735
736 class SliceAdmin(PlanetStackBaseAdmin):
737     form = SliceForm
738     fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
739     fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
740     readonly_fields = ('backend_status_text', )
741     list_display = ('backend_status_icon', 'slicename', 'site','serviceClass', 'slice_url', 'max_slivers')
742     list_display_links = ('backend_status_icon', 'slicename', )
743     inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
744
745     user_readonly_fields = fieldList
746
747     suit_form_tabs =(('general', 'Slice Details'),
748         ('slicenetworks','Networks'),
749         ('sliceprivileges','Privileges'),
750         ('slivers','Slivers'),
751         ('tags','Tags'),
752         ('reservations','Reservations'),
753     )
754
755     def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
756         deployment_nodes = []
757         for node in Node.objects.all():
758             deployment_nodes.append( (node.deployment.id, node.id, node.name) )
759
760         deployment_flavors = []
761         for flavor in Flavor.objects.all():
762             for deployment in flavor.deployments.all():
763                 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
764
765         site_login_bases = []
766         for site in Site.objects.all():
767             site_login_bases.append((site.id, site.login_base)) 
768         
769         context["deployment_nodes"] = deployment_nodes
770         context["deployment_flavors"] = deployment_flavors
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