add support for fine-grained field permissions for user model
[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, AdminPasswordChangeForm
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 # this block of stuff is needed for UserAdmin
20 from django.db import transaction
21 from django.utils.decorators import method_decorator
22 from django.views.decorators.csrf import csrf_protect
23 from django.views.decorators.debug import sensitive_post_parameters
24 csrf_protect_m = method_decorator(csrf_protect)
25 sensitive_post_parameters_m = method_decorator(sensitive_post_parameters())
26
27 import django_evolution
28
29 def backend_icon(obj): # backend_status, enacted, updated):
30     #return "%s %s %s" % (str(obj.updated), str(obj.enacted), str(obj.backend_status))
31     if (obj.enacted is not None) and obj.enacted >= obj.updated:
32         return '<span style="min-width:16px;"><img src="/static/admin/img/icon_success.gif"></span>'
33     else:
34         if obj.backend_status == "Provisioning in progress" or obj.backend_status=="":
35             return '<span style="min-width:16px;" title="%s"><img src="/static/admin/img/icon_clock.gif"></span>' % obj.backend_status
36         else:
37             return '<span style="min-width:16px;" title="%s"><img src="/static/admin/img/icon_error.gif"></span>' % obj.backend_status
38
39 def backend_text(obj):
40     icon = backend_icon(obj)
41     if (obj.enacted is not None) and obj.enacted >= obj.updated:
42         return "%s %s" % (icon, "successfully enacted") # enacted on %s" % str(obj.enacted))
43     else:
44         return "%s %s" % (icon, obj.backend_status)
45
46 class PlainTextWidget(forms.HiddenInput):
47     input_type = 'hidden'
48
49     def render(self, name, value, attrs=None):
50         if value is None:
51             value = ''
52         return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
53
54 class PermissionCheckingAdmin(admin.ModelAdmin):
55     # call save_by_user and delete_by_user instead of save and delete
56
57     def has_add_permission(self, request, obj=None):
58         return (not self.__user_is_readonly(request))
59
60     def has_delete_permission(self, request, obj=None):
61         return (not self.__user_is_readonly(request))
62
63     def save_model(self, request, obj, form, change):
64         if self.__user_is_readonly(request):
65             # this 'if' might be redundant if save_by_user is implemented right
66             raise PermissionDenied
67
68         obj.caller = request.user
69         # update openstack connection to use this site/tenant
70         obj.save_by_user(request.user)
71
72     def delete_model(self, request, obj):
73         obj.delete_by_user(request.user)
74
75     def save_formset(self, request, form, formset, change):
76         instances = formset.save(commit=False)
77         for instance in instances:
78             instance.save_by_user(request.user)
79
80         # BUG in django 1.7? Objects are not deleted by formset.save if
81         # commit is False. So let's delete them ourselves.
82         #
83         # code from forms/models.py save_existing_objects()
84         try:
85             forms_to_delete = formset.deleted_forms\r
86         except AttributeError:\r
87             forms_to_delete = []
88         if formset.initial_forms:
89             for form in formset.initial_forms:
90                 obj = form.instance
91                 if form in forms_to_delete:
92                     if obj.pk is None:
93                         continue
94                     formset.deleted_objects.append(obj)
95                     obj.delete()
96
97         formset.save_m2m()
98
99     def get_actions(self,request):
100         actions = super(PermissionCheckingAdmin,self).get_actions(request)
101
102         if self.__user_is_readonly(request):
103             if 'delete_selected' in actions:
104                 del actions['delete_selected']
105
106         return actions
107
108     def change_view(self,request,object_id, extra_context=None):
109         if self.__user_is_readonly(request):
110             if not hasattr(self, "readonly_save"):\r
111                 # save the original readonly fields\r
112                 self.readonly_save = self.readonly_fields\r
113                 self.inlines_save = self.inlines\r
114             if hasattr(self, "user_readonly_fields"):\r
115                 self.readonly_fields=self.user_readonly_fields\r
116             if hasattr(self, "user_readonly_inlines"):\r
117                 self.inlines = self.user_readonly_inlines\r
118         else:\r
119             if hasattr(self, "readonly_save"):\r
120                 # restore the original readonly fields\r
121                 self.readonly_fields = self.readonly_save\r
122             if hasattr(self, "inlines_save"):\r
123                 self.inlines = self.inlines_save
124
125         try:
126             return super(PermissionCheckingAdmin, self).change_view(request, object_id, extra_context=extra_context)
127         except PermissionDenied:
128             pass
129         if request.method == 'POST':
130             raise PermissionDenied
131         request.readonly = True
132         return super(PermissionCheckingAdmin, self).change_view(request, object_id, extra_context=extra_context)
133
134     def __user_is_readonly(self, request):
135         return request.user.isReadOnlyUser()
136
137     def backend_status_text(self, obj):
138         return mark_safe(backend_text(obj))
139
140     def backend_status_icon(self, obj):
141         return mark_safe(backend_icon(obj))
142     backend_status_icon.short_description = ""
143
144 class ReadOnlyAwareAdmin(PermissionCheckingAdmin):
145     pass
146
147 class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
148     save_on_top = False
149
150 class SingletonAdmin (ReadOnlyAwareAdmin):
151     def has_add_permission(self, request):
152         if not super(SingletonAdmin, self).has_add_permission(request):
153             return False
154
155         num_objects = self.model.objects.count()
156         if num_objects >= 1:
157             return False
158         else:
159             return True
160
161 class PlStackTabularInline(admin.TabularInline):
162     def __init__(self, *args, **kwargs):
163         super(PlStackTabularInline, self).__init__(*args, **kwargs)
164
165         # InlineModelAdmin as no get_fields() method, so in order to add
166         # the selflink field, we override __init__ to modify self.fields and
167         # self.readonly_fields.
168
169         self.setup_selflink()
170
171     def get_change_url(self, model, id):
172         """ Get the URL to a change form in the admin for this model """
173         reverse_path = "admin:%s_change" % (model._meta.db_table)
174         try:
175             url = reverse(reverse_path, args=(id,))
176         except NoReverseMatch:
177             return None
178
179         return url
180
181     def setup_selflink(self):
182         if hasattr(self, "selflink_fieldname"):
183             """ self.selflink_model can be defined to punch through a relation
184                 to its target object. For example, in SliceNetworkInline, set
185                 selflink_model = "network", and the URL will lead to the Network
186                 object instead of trying to bring up a change view of the
187                 SliceNetwork object.
188             """
189             self.selflink_model = getattr(self.model,self.selflink_fieldname).field.rel.to
190         else:
191             self.selflink_model = self.model
192
193         url = self.get_change_url(self.selflink_model, 0)
194
195         # We don't have an admin for this object, so don't create the
196         # selflink.
197         if (url == None):
198             return
199
200         # Since we need to add "selflink" to the field list, we need to create
201         # self.fields if it is None.
202         if (self.fields is None):
203             self.fields = []
204             for f in self.model._meta.fields:
205                 if f.editable and f.name != "id":
206                     self.fields.append(f.name)
207
208         self.fields = tuple(self.fields) + ("selflink", )
209
210         if self.readonly_fields is None:
211             self.readonly_fields = ()
212
213         self.readonly_fields = tuple(self.readonly_fields) + ("selflink", )
214
215     def selflink(self, obj):
216         if hasattr(self, "selflink_fieldname"):
217             obj = getattr(obj, self.selflink_fieldname)
218
219         if obj.id:
220             url = self.get_change_url(self.selflink_model, obj.id)
221             return "<a href='%s'>Details</a>" % str(url)
222         else:\r
223             return "Not present"\r
224
225     selflink.allow_tags = True
226     selflink.short_description = "Details"
227
228     def has_add_permission(self, request):
229         return not request.user.isReadOnlyUser()
230
231     def get_readonly_fields(self, request, obj=None):
232         readonly_fields = list(self.readonly_fields)[:]
233         if request.user.isReadOnlyUser():
234             for field in self.fields:
235                 if not field in readonly_fields:
236                     readonly_fields.append(field)
237         return readonly_fields
238
239     def backend_status_icon(self, obj):
240         return mark_safe(backend_icon(obj))
241     backend_status_icon.short_description = ""
242
243 class PlStackGenericTabularInline(generic.GenericTabularInline):
244     def has_add_permission(self, request):
245         return not request.user.isReadOnlyUser()
246
247     def get_readonly_fields(self, request, obj=None):
248         readonly_fields = list(self.readonly_fields)[:]
249         if request.user.isReadOnlyUser():
250             for field in self.fields:
251                 if not field in readonly_fields:
252                     readonly_fields.append(field)
253         return readonly_fields
254
255     def backend_status_icon(self, obj):
256         return mark_safe(backend_icon(obj))
257     backend_status_icon.short_description = ""
258
259 class ReservationInline(PlStackTabularInline):
260     model = Reservation
261     extra = 0
262     suit_classes = 'suit-tab suit-tab-reservations'
263
264     def queryset(self, request):
265         return Reservation.select_by_user(request.user)
266
267 class TagInline(PlStackGenericTabularInline):
268     model = Tag
269     extra = 0
270     suit_classes = 'suit-tab suit-tab-tags'
271     fields = ['service', 'name', 'value']
272
273     def queryset(self, request):
274         return Tag.select_by_user(request.user)
275
276 class NetworkLookerUpper:
277     """ This is a callable that looks up a network name in a sliver and returns
278         the ip address for that network.
279     """
280
281     byNetworkName = {}    # class variable
282
283     def __init__(self, name):
284         self.short_description = name
285         self.__name__ = name
286         self.network_name = name
287
288     def __call__(self, obj):
289         if obj is not None:
290             for nbs in obj.networksliver_set.all():
291                 if (nbs.network.name == self.network_name):
292                     return nbs.ip
293         return ""
294
295     def __str__(self):
296         return self.network_name
297
298     @staticmethod
299     def get(network_name):
300         """ We want to make sure we alwars return the same NetworkLookerUpper
301             because sometimes django will cause them to be instantiated multiple
302             times (and we don't want different ones in form.fields vs
303             SliverInline.readonly_fields).
304         """
305         if network_name not in NetworkLookerUpper.byNetworkName:
306             NetworkLookerUpper.byNetworkName[network_name] = NetworkLookerUpper(network_name)
307         return NetworkLookerUpper.byNetworkName[network_name]
308
309 class SliverInline(PlStackTabularInline):
310     model = Sliver
311     fields = ['backend_status_icon', 'all_ips_string', 'instance_name', 'slice', 'deploymentNetwork', 'flavor', 'image', 'node']
312     extra = 0
313     readonly_fields = ['backend_status_icon', 'all_ips_string', 'instance_name']
314     suit_classes = 'suit-tab suit-tab-slivers'
315
316     def queryset(self, request):
317         return Sliver.select_by_user(request.user)
318
319     def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
320         if db_field.name == 'deploymentNetwork':
321            kwargs['queryset'] = Deployment.select_by_acl(request.user)
322            kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_deployment_changed(this);"})
323         elif db_field.name == 'flavor':
324            kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_flavor_changed(this);"})
325
326         field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
327
328         return field
329
330 class SiteInline(PlStackTabularInline):
331     model = Site
332     extra = 0
333     suit_classes = 'suit-tab suit-tab-sites'
334
335     def queryset(self, request):
336         return Site.select_by_user(request.user)
337
338 class UserInline(PlStackTabularInline):
339     model = User
340     fields = ['backend_status_icon', 'email', 'firstname', 'lastname']
341     readonly_fields = ('backend_status_icon', )
342     extra = 0
343     suit_classes = 'suit-tab suit-tab-users'
344
345     def queryset(self, request):
346         return User.select_by_user(request.user)
347
348 class SliceInline(PlStackTabularInline):
349     model = Slice
350     fields = ['backend_status_icon', 'name', 'site', 'serviceClass', 'service']
351     readonly_fields = ('backend_status_icon', )
352     extra = 0
353     suit_classes = 'suit-tab suit-tab-slices'
354
355     def queryset(self, request):
356         return Slice.select_by_user(request.user)
357
358 class NodeInline(PlStackTabularInline):
359     model = Node
360     extra = 0
361     suit_classes = 'suit-tab suit-tab-nodes'
362     fields = ['backend_status_icon', 'name','deployment','site']
363     readonly_fields = ('backend_status_icon', )
364
365 class DeploymentPrivilegeInline(PlStackTabularInline):
366     model = DeploymentPrivilege
367     extra = 0
368     suit_classes = 'suit-tab suit-tab-deploymentprivileges'
369     fields = ['backend_status_icon', 'user','role','deployment']
370     readonly_fields = ('backend_status_icon', )
371
372     def queryset(self, request):
373         return DeploymentPrivilege.select_by_user(request.user)
374
375 class SitePrivilegeInline(PlStackTabularInline):
376     model = SitePrivilege
377     extra = 0
378     suit_classes = 'suit-tab suit-tab-siteprivileges'
379     fields = ['backend_status_icon', 'user','site', 'role']
380     readonly_fields = ('backend_status_icon', )
381
382     def formfield_for_foreignkey(self, db_field, request, **kwargs):
383         if db_field.name == 'site':
384             kwargs['queryset'] = Site.select_by_user(request.user)
385
386         if db_field.name == 'user':
387             kwargs['queryset'] = User.select_by_user(request.user)
388         return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
389
390     def queryset(self, request):
391         return SitePrivilege.select_by_user(request.user)
392
393 class SiteDeploymentInline(PlStackTabularInline):
394     model = SiteDeployments
395     extra = 0
396     suit_classes = 'suit-tab suit-tab-deployments'
397     fields = ['backend_status_icon', 'deployment','site']
398     readonly_fields = ('backend_status_icon', )
399
400     def formfield_for_foreignkey(self, db_field, request, **kwargs):
401         if db_field.name == 'site':
402             kwargs['queryset'] = Site.select_by_user(request.user)
403
404         if db_field.name == 'deployment':
405             kwargs['queryset'] = Deployment.select_by_user(request.user)
406         return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
407
408     def queryset(self, request):
409         return SiteDeployments.select_by_user(request.user)
410
411
412 class SlicePrivilegeInline(PlStackTabularInline):
413     model = SlicePrivilege
414     suit_classes = 'suit-tab suit-tab-sliceprivileges'
415     extra = 0
416     fields = ('backend_status_icon', 'user', 'slice', 'role')
417     readonly_fields = ('backend_status_icon', )
418
419     def formfield_for_foreignkey(self, db_field, request, **kwargs):
420         if db_field.name == 'slice':
421            kwargs['queryset'] = Slice.select_by_user(request.user)
422         if db_field.name == 'user':
423            kwargs['queryset'] = User.select_by_user(request.user)
424
425         return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
426
427     def queryset(self, request):
428         return SlicePrivilege.select_by_user(request.user)
429
430 class SliceNetworkInline(PlStackTabularInline):
431     model = Network.slices.through
432     selflink_fieldname = "network"
433     extra = 0
434     verbose_name = "Network Connection"
435     verbose_name_plural = "Network Connections"
436     suit_classes = 'suit-tab suit-tab-slicenetworks'
437     fields = ['backend_status_icon', 'network']
438     readonly_fields = ('backend_status_icon', )
439
440 class ImageDeploymentsInline(PlStackTabularInline):
441     model = ImageDeployments
442     extra = 0
443     verbose_name = "Image Deployments"
444     verbose_name_plural = "Image Deployments"
445     suit_classes = 'suit-tab suit-tab-imagedeployments'
446     fields = ['backend_status_icon', 'image', 'deployment', 'glance_image_id']
447     readonly_fields = ['backend_status_icon', 'glance_image_id']
448
449 class SliceRoleAdmin(PlanetStackBaseAdmin):
450     model = SliceRole
451     pass
452
453 class SiteRoleAdmin(PlanetStackBaseAdmin):
454     model = SiteRole
455     pass
456
457 class DeploymentAdminForm(forms.ModelForm):
458     sites = forms.ModelMultipleChoiceField(
459         queryset=Site.objects.all(),
460         required=False,
461         help_text="Select which sites are allowed to host nodes in this deployment",
462         widget=FilteredSelectMultiple(
463             verbose_name=('Sites'), is_stacked=False
464         )
465     )
466     images = forms.ModelMultipleChoiceField(
467         queryset=Image.objects.all(),
468         required=False,
469         help_text="Select which images should be deployed on this deployment",
470         widget=FilteredSelectMultiple(
471             verbose_name=('Images'), is_stacked=False
472         )
473     )
474     flavors = forms.ModelMultipleChoiceField(
475         queryset=Flavor.objects.all(),
476         required=False,
477         help_text="Select which flavors should be usable on this deployment",
478         widget=FilteredSelectMultiple(
479             verbose_name=('Flavors'), is_stacked=False
480         )
481     )
482     class Meta:
483         model = Deployment
484         many_to_many = ["flavors",]
485
486     def __init__(self, *args, **kwargs):
487       request = kwargs.pop('request', None)
488       super(DeploymentAdminForm, self).__init__(*args, **kwargs)
489
490       self.fields['accessControl'].initial = "allow site " + request.user.site.name
491
492       if self.instance and self.instance.pk:
493         self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments_set.all()]
494         self.fields['images'].initial = [x.image for x in self.instance.imagedeployments_set.all()]
495         self.fields['flavors'].initial = self.instance.flavors.all()
496
497     def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
498         """ helper function for handling m2m relations from the MultipleChoiceField
499
500             this_obj: the source object we want to link from
501
502             selected_objs: a list of destination objects we want to link to
503
504             all_relations: the full set of relations involving this_obj, including ones we don't want
505
506             relation_class: the class that implements the relation from source to dest
507
508             local_attrname: field name representing this_obj in relation_class
509
510             foreign_attrname: field name representing selected_objs in relation_class
511
512             This function will remove all newobjclass relations from this_obj
513             that are not contained in selected_objs, and add any relations that
514             are in selected_objs but don't exist in the data model yet.
515         """
516
517         existing_dest_objs = []
518         for relation in list(all_relations):
519             if getattr(relation, foreign_attrname) not in selected_objs:
520                 #print "deleting site", sdp.site
521                 relation.delete()
522             else:
523                 existing_dest_objs.append(getattr(relation, foreign_attrname))
524
525         for dest_obj in selected_objs:
526             if dest_obj not in existing_dest_objs:
527                 #print "adding site", site
528                 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
529                 relation = relation_class(**kwargs)
530                 relation.save()
531
532     def save(self, commit=True):
533       deployment = super(DeploymentAdminForm, self).save(commit=False)
534
535       deployment.flavors = self.cleaned_data['flavors']
536
537       if commit:
538         deployment.save()
539
540       if deployment.pk:
541         # save_m2m() doesn't seem to work with 'through' relations. So we
542         #    create/destroy the through models ourselves. There has to be
543         #    a better way...
544
545         self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments_set.all(), SiteDeployments, "deployment", "site")
546         self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments_set.all(), ImageDeployments, "deployment", "image")
547
548       self.save_m2m()
549
550       return deployment
551
552 class DeploymentAdminROForm(DeploymentAdminForm):
553     def save(self, commit=True):
554         raise PermissionDenied
555
556 class SiteAssocInline(PlStackTabularInline):
557     model = Site.deployments.through
558     extra = 0
559     suit_classes = 'suit-tab suit-tab-sites'
560
561 class DeploymentAdmin(PlanetStackBaseAdmin):
562     model = Deployment
563     fieldList = ['backend_status_text', 'name', 'availability_zone', 'sites', 'images', 'flavors', 'accessControl']
564     fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
565     inlines = [DeploymentPrivilegeInline,NodeInline,TagInline] # ,ImageDeploymentsInline]
566     list_display = ['backend_status_icon', 'name']
567     list_display_links = ('backend_status_icon', 'name', )
568     readonly_fields = ('backend_status_text', )
569
570     user_readonly_fields = ['name']
571
572     suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags')) # ,('imagedeployments','Images'))
573
574     def get_form(self, request, obj=None, **kwargs):
575         if request.user.isReadOnlyUser():
576             kwargs["form"] = DeploymentAdminROForm
577         else:
578             kwargs["form"] = DeploymentAdminForm
579         adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
580
581         # from stackexchange: pass the request object into the form
582
583         class AdminFormMetaClass(adminForm):
584            def __new__(cls, *args, **kwargs):
585                kwargs['request'] = request
586                return adminForm(*args, **kwargs)
587
588         return AdminFormMetaClass
589
590 class ServiceAttrAsTabInline(PlStackTabularInline):
591     model = ServiceAttribute
592     fields = ['name','value']
593     extra = 0
594     suit_classes = 'suit-tab suit-tab-serviceattrs'
595
596 class ServiceAdmin(PlanetStackBaseAdmin):
597     list_display = ("backend_status_icon","name","description","versionNumber","enabled","published")
598     list_display_links = ('backend_status_icon', 'name', )
599     fieldList = ["backend_status_text","name","description","versionNumber","enabled","published"]
600     fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
601     inlines = [ServiceAttrAsTabInline,SliceInline]
602     readonly_fields = ('backend_status_text', )
603
604     user_readonly_fields = fieldList
605
606     suit_form_tabs =(('general', 'Service Details'),
607         ('slices','Slices'),
608         ('serviceattrs','Additional Attributes'),
609     )
610
611 class SiteAdmin(PlanetStackBaseAdmin):
612     fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
613     fieldsets = [
614         (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
615         #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
616     ]
617     suit_form_tabs =(('general', 'Site Details'),
618         ('users','Users'),
619         ('siteprivileges','Privileges'),
620         ('deployments','Deployments'),
621         ('slices','Slices'),
622         ('nodes','Nodes'),
623         ('tags','Tags'),
624     )
625     readonly_fields = ['backend_status_text', 'accountLink']
626
627     user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
628
629     list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
630     list_display_links = ('backend_status_icon', 'name', )
631     filter_horizontal = ('deployments',)
632     inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentInline]
633     search_fields = ['name']
634
635     def queryset(self, request):
636         return Site.select_by_user(request.user)
637
638     def get_formsets(self, request, obj=None):
639         for inline in self.get_inline_instances(request, obj):
640             # hide MyInline in the add view
641             if obj is None:
642                 continue
643             if isinstance(inline, SliceInline):
644                 inline.model.caller = request.user
645             yield inline.get_formset(request, obj)
646
647     def get_formsets(self, request, obj=None):
648         for inline in self.get_inline_instances(request, obj):
649             # hide MyInline in the add view
650             if obj is None:
651                 continue
652             if isinstance(inline, SliverInline):
653                 inline.model.caller = request.user
654             yield inline.get_formset(request, obj)
655
656     def accountLink(self, obj):
657         link_obj = obj.accounts.all()
658         if link_obj:
659             reverse_path = "admin:core_account_change"
660             url = reverse(reverse_path, args =(link_obj[0].id,))
661             return "<a href='%s'>%s</a>" % (url, "view billing details")
662         else:
663             return "no billing data for this site"
664     accountLink.allow_tags = True
665     accountLink.short_description = "Billing"
666
667     def save_model(self, request, obj, form, change):
668         # update openstack connection to use this site/tenant
669         obj.save_by_user(request.user) 
670
671     def delete_model(self, request, obj):
672         obj.delete_by_user(request.user)
673         
674
675 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
676     fieldList = ['backend_status_text', 'user', 'site', 'role']
677     fieldsets = [
678         (None, {'fields': fieldList, 'classes':['collapse']})
679     ]
680     readonly_fields = ('backend_status_text', )
681     list_display = ('backend_status_icon', 'user', 'site', 'role')
682     list_display_links = list_display
683     user_readonly_fields = fieldList
684     user_readonly_inlines = []
685
686     def formfield_for_foreignkey(self, db_field, request, **kwargs):
687         if db_field.name == 'site':
688             if not request.user.is_admin:
689                 # only show sites where user is an admin or pi
690                 sites = set()
691                 for site_privilege in SitePrivilege.objects.filer(user=request.user):
692                     if site_privilege.role.role_type in ['admin', 'pi']:
693                         sites.add(site_privilege.site)
694                 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
695
696         if db_field.name == 'user':
697             if not request.user.is_admin:
698                 # only show users from sites where caller has admin or pi role
699                 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
700                 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
701                 sites = [site_privilege.site for site_privilege in site_privileges]
702                 site_privileges = SitePrivilege.objects.filter(site__in=sites)
703                 emails = [site_privilege.user.email for site_privilege in site_privileges]
704                 users = User.objects.filter(email__in=emails)
705                 kwargs['queryset'] = users
706
707         return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
708
709     def queryset(self, request):
710         # admins can see all privileges. Users can only see privileges at sites
711         # where they have the admin role or pi role.
712         qs = super(SitePrivilegeAdmin, self).queryset(request)
713         #if not request.user.is_admin:
714         #    roles = Role.objects.filter(role_type__in=['admin', 'pi'])
715         #    site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
716         #    login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
717         #    sites = Site.objects.filter(login_base__in=login_bases)
718         #    qs = qs.filter(site__in=sites)
719         return qs
720
721 class SliceForm(forms.ModelForm):
722     class Meta:
723         model = Slice
724         widgets = {
725             'service': LinkedSelect
726         }
727
728     def clean(self):
729         cleaned_data = super(SliceForm, self).clean()
730         name = cleaned_data.get('name')
731         site_id = cleaned_data.get('site')
732         site = Slice.objects.get(id=site_id)
733         if not name.startswith(site.login_base):
734             raise forms.ValidationError('slice name must begin with %s' % site.login_base)
735         return cleaned_data
736
737 class SliceAdmin(PlanetStackBaseAdmin):
738     form = SliceForm
739     fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
740     fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
741     readonly_fields = ('backend_status_text', )
742     list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_slivers')
743     list_display_links = ('backend_status_icon', 'name', )
744     inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
745
746     user_readonly_fields = fieldList
747
748     suit_form_tabs =(('general', 'Slice Details'),
749         ('slicenetworks','Networks'),
750         ('sliceprivileges','Privileges'),
751         ('slivers','Slivers'),
752         ('tags','Tags'),
753         ('reservations','Reservations'),
754     )
755
756     def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
757         deployment_nodes = []
758         for node in Node.objects.all():
759             deployment_nodes.append( (node.deployment.id, node.id, node.name) )
760
761         deployment_flavors = []
762         for flavor in Flavor.objects.all():
763             for deployment in flavor.deployments.all():
764                 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
765
766         deployment_images = []
767         for image in Image.objects.all():
768             for imageDeployment in image.imagedeployments_set.all():
769                 deployment_images.append( (imageDeployment.deployment.id, image.id, image.name) )
770
771         site_login_bases = []
772         for site in Site.objects.all():
773             site_login_bases.append((site.id, site.login_base))
774
775         context["deployment_nodes"] = deployment_nodes
776         context["deployment_flavors"] = deployment_flavors
777         context["deployment_images"] = deployment_images
778         context["site_login_bases"] = site_login_bases
779         return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
780
781     def formfield_for_foreignkey(self, db_field, request, **kwargs):
782         if db_field.name == 'site':
783             kwargs['queryset'] = Site.select_by_user(request.user)
784             kwargs['widget'] = forms.Select(attrs={'onChange': "update_slice_prefix(this, $($(this).closest('fieldset')[0]).find('.field-name input')[0].id)"})
785
786         return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
787
788     def queryset(self, request):
789         # admins can see all keys. Users can only see slices they belong to.
790         return Slice.select_by_user(request.user)
791
792     def get_formsets(self, request, obj=None):
793         for inline in self.get_inline_instances(request, obj):
794             # hide MyInline in the add view
795             if obj is None:
796                 continue
797             if isinstance(inline, SliverInline):
798                 inline.model.caller = request.user
799             yield inline.get_formset(request, obj)
800
801
802 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
803     fieldsets = [
804         (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
805     ]
806     readonly_fields = ('backend_status_text', )
807     list_display = ('backend_status_icon', 'user', 'slice', 'role')
808     list_display_links = list_display
809
810     user_readonly_fields = ['user', 'slice', 'role']
811     user_readonly_inlines = []
812
813     def formfield_for_foreignkey(self, db_field, request, **kwargs):
814         if db_field.name == 'slice':
815             kwargs['queryset'] = Slice.select_by_user(request.user)
816         
817         if db_field.name == 'user':
818             kwargs['queryset'] = User.select_by_user(request.user)
819
820         return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
821
822     def queryset(self, request):
823         # admins can see all memberships. Users can only see memberships of
824         # slices where they have the admin role.
825         return SlicePrivilege.select_by_user(request.user)
826
827     def save_model(self, request, obj, form, change):
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.save()
833
834     def delete_model(self, request, obj):
835         # update openstack connection to use this site/tenant
836         auth = request.session.get('auth', {})
837         auth['tenant'] = obj.slice.slicename
838         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
839         obj.delete()
840
841
842 class ImageAdmin(PlanetStackBaseAdmin):
843
844     fieldsets = [('Image Details',
845                    {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
846                     'classes': ['suit-tab suit-tab-general']})
847                ]
848     readonly_fields = ('backend_status_text', )
849
850     suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
851
852     inlines = [SliverInline, ImageDeploymentsInline]
853
854     user_readonly_fields = ['name', 'disk_format', 'container_format']
855
856     list_display = ['backend_status_icon', 'name']
857     list_display_links = ('backend_status_icon', 'name', )
858
859 class NodeForm(forms.ModelForm):
860     class Meta:
861         widgets = {
862             'site': LinkedSelect,
863             'deployment': LinkedSelect
864         }
865
866 class NodeAdmin(PlanetStackBaseAdmin):
867     form = NodeForm
868     list_display = ('backend_status_icon', 'name', 'site', 'deployment')
869     list_display_links = ('backend_status_icon', 'name', )
870     list_filter = ('deployment',)
871
872     inlines = [TagInline,SliverInline]
873     fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
874     readonly_fields = ('backend_status_text', )
875
876     user_readonly_fields = ['name','site','deployment']
877     user_readonly_inlines = [TagInline,SliverInline]
878
879     suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
880
881
882 class SliverForm(forms.ModelForm):
883     class Meta:
884         model = Sliver
885         ip = forms.CharField(widget=PlainTextWidget)
886         instance_name = forms.CharField(widget=PlainTextWidget)
887         widgets = {
888             'ip': PlainTextWidget(),
889             'instance_name': PlainTextWidget(),
890             'slice': LinkedSelect,
891             'deploymentNetwork': LinkedSelect,
892             'node': LinkedSelect,
893             'image': LinkedSelect
894         }
895
896 class TagAdmin(PlanetStackBaseAdmin):
897     list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
898     list_display_links = list_display
899     user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
900     user_readonly_inlines = []
901
902 class SliverAdmin(PlanetStackBaseAdmin):
903     form = SliverForm
904     fieldsets = [
905         ('Sliver Details', {'fields': ['backend_status_text', 'slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
906     ]
907     readonly_fields = ('backend_status_text', )
908     list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'flavor', 'image', 'node', 'deploymentNetwork']
909     list_display_links = ('backend_status_icon', 'ip',)
910
911     suit_form_tabs =(('general', 'Sliver Details'),
912         ('tags','Tags'),
913     )
914
915     inlines = [TagInline]
916
917     user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image']
918
919     def formfield_for_foreignkey(self, db_field, request, **kwargs):
920         if db_field.name == 'slice':
921             kwargs['queryset'] = Slice.select_by_user(request.user)
922
923         return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
924
925     def queryset(self, request):
926         # admins can see all slivers. Users can only see slivers of
927         # the slices they belong to.
928         return Sliver.select_by_user(request.user)
929
930
931     def get_formsets(self, request, obj=None):
932         # make some fields read only if we are updating an existing record
933         if obj == None:
934             #self.readonly_fields = ('ip', 'instance_name')
935             self.readonly_fields = ('backend_status_text')
936         else:
937             self.readonly_fields = ('backend_status_text')
938             #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
939
940         for inline in self.get_inline_instances(request, obj):
941             # hide MyInline in the add view
942             if obj is None:
943                 continue
944             if isinstance(inline, SliverInline):
945                 inline.model.caller = request.user
946             yield inline.get_formset(request, obj)
947
948     #def save_model(self, request, obj, form, change):
949     #    # update openstack connection to use this site/tenant
950     #    auth = request.session.get('auth', {})
951     #    auth['tenant'] = obj.slice.name
952     #    obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
953     #    obj.creator = request.user
954     #    obj.save()
955
956     #def delete_model(self, request, obj):
957     #    # update openstack connection to use this site/tenant
958     #    auth = request.session.get('auth', {})
959     #    auth['tenant'] = obj.slice.name
960     #    obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
961     #    obj.delete()
962
963 class UserCreationForm(forms.ModelForm):
964     """A form for creating new users. Includes all the required
965     fields, plus a repeated password."""
966     password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
967     password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
968
969     class Meta:
970         model = User
971         fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
972
973     def clean_password2(self):
974         # Check that the two password entries match
975         password1 = self.cleaned_data.get("password1")
976         password2 = self.cleaned_data.get("password2")
977         if password1 and password2 and password1 != password2:
978             raise forms.ValidationError("Passwords don't match")
979         return password2
980
981     def save(self, commit=True):
982         # Save the provided password in hashed format
983         user = super(UserCreationForm, self).save(commit=False)
984         user.password = self.cleaned_data["password1"]
985         #user.set_password(self.cleaned_data["password1"])
986         if commit:
987             user.save()
988         return user
989
990
991 class UserChangeForm(forms.ModelForm):
992     """A form for updating users. Includes all the fields on
993     the user, but replaces the password field with admin's
994     password hash display field.
995     """
996     password = ReadOnlyPasswordHashField(label='Password',
997                    help_text= '<a href=\"password/\">Change Password</a>.')
998
999     class Meta:
1000         model = User
1001
1002     def clean_password(self):
1003         # Regardless of what the user provides, return the initial value.
1004         # This is done here, rather than on the field, because the
1005         # field does not have access to the initial value
1006         return self.initial["password"]
1007
1008 class UserDashboardViewInline(PlStackTabularInline):
1009     model = UserDashboardView
1010     extra = 0
1011     suit_classes = 'suit-tab suit-tab-dashboards'
1012     fields = ['user', 'dashboardView', 'order']
1013
1014 class UserAdmin(PlanetStackBaseAdmin):
1015     class Meta:
1016         app_label = "core"
1017
1018     add_form_template = 'admin/auth/user/add_form.html'
1019     change_user_password_template = None
1020
1021     # The forms to add and change user instances
1022     form = UserChangeForm
1023     add_form = UserCreationForm
1024     change_password_form = AdminPasswordChangeForm
1025
1026     # The fields to be used in displaying the User model.
1027     # These override the definitions on the base UserAdmin
1028     # that reference specific fields on auth.User.
1029     list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
1030     list_filter = ('site',)
1031     inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
1032
1033     fieldListLoginDetails = ['backend_status_text', 'email','site','password','is_active','is_readonly','is_admin','public_key']
1034     fieldListContactInfo = ['firstname','lastname','phone','timezone']
1035
1036     fieldsets = (
1037         ('Login Details', {'fields': ['backend_status_text', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1038         ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1039         #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1040         #('Important dates', {'fields': ('last_login',)}),
1041     )
1042     add_fieldsets = (
1043         (None, {
1044             'classes': ('wide',),
1045             'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
1046         ),
1047     )
1048     readonly_fields = ('backend_status_text', )
1049     search_fields = ('email',)
1050     ordering = ('email',)
1051     filter_horizontal = ()
1052
1053     user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1054
1055     suit_form_tabs =(('general','Login Details'),
1056                      ('contact','Contact Information'),
1057                      ('sliceprivileges','Slice Privileges'),
1058                      ('siteprivileges','Site Privileges'),
1059                      ('deploymentprivileges','Deployment Privileges'),
1060                      ('dashboards','Dashboard Views'))
1061
1062     def formfield_for_foreignkey(self, db_field, request, **kwargs):
1063         if db_field.name == 'site':
1064             kwargs['queryset'] = Site.select_by_user(request.user)
1065
1066         return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1067
1068     def queryset(self, request):
1069         return User.select_by_user(request.user)
1070
1071     # ------------------------------------------------------------------------
1072     # stuff copied from ModelAdmin.UserAdmin
1073     # ------------------------------------------------------------------------
1074     def get_fieldsets(self, request, obj=None):
1075         if not obj:\r
1076             return self.add_fieldsets\r
1077         return super(UserAdmin, self).get_fieldsets(request, obj)
1078
1079     def get_form(self, request, obj=None, **kwargs):
1080         """\r
1081         Use special form during user creation\r
1082         """\r
1083         defaults = {}\r
1084         if obj is None:\r
1085             defaults['form'] = self.add_form\r
1086         defaults.update(kwargs)\r
1087         return super(UserAdmin, self).get_form(request, obj, **defaults)\r
1088 \r
1089     def get_urls(self):\r
1090         from django.conf.urls import patterns\r
1091         return patterns('',\r
1092             (r'^(\d+)/password/$',\r
1093              self.admin_site.admin_view(self.user_change_password))\r
1094         ) + super(UserAdmin, self).get_urls()\r
1095 \r
1096     def lookup_allowed(self, lookup, value):\r
1097         # See #20078: we don't want to allow any lookups involving passwords.\r
1098         if lookup.startswith('password'):\r
1099             return False\r
1100         return super(UserAdmin, self).lookup_allowed(lookup, value)\r
1101 \r
1102     @sensitive_post_parameters_m\r
1103     @csrf_protect_m\r
1104     @transaction.atomic\r
1105     def add_view(self, request, form_url='', extra_context=None):\r
1106         # It's an error for a user to have add permission but NOT change\r
1107         # permission for users. If we allowed such users to add users, they\r
1108         # could create superusers, which would mean they would essentially have\r
1109         # the permission to change users. To avoid the problem entirely, we\r
1110         # disallow users from adding users if they don't have change\r
1111         # permission.\r
1112         if not self.has_change_permission(request):\r
1113             if self.has_add_permission(request) and settings.DEBUG:\r
1114                 # Raise Http404 in debug mode so that the user gets a helpful\r
1115                 # error message.\r
1116                 raise Http404(\r
1117                     'Your user does not have the "Change user" permission. In '\r
1118                     'order to add users, Django requires that your user '\r
1119                     'account have both the "Add user" and "Change user" '\r
1120                     'permissions set.')\r
1121             raise PermissionDenied\r
1122         if extra_context is None:\r
1123             extra_context = {}\r
1124         username_field = self.model._meta.get_field(self.model.USERNAME_FIELD)\r
1125         defaults = {\r
1126             'auto_populated_fields': (),\r
1127             'username_help_text': username_field.help_text,\r
1128         }\r
1129         extra_context.update(defaults)\r
1130         return super(UserAdmin, self).add_view(request, form_url,\r
1131                                                extra_context)\r
1132 \r
1133     @sensitive_post_parameters_m\r
1134     def user_change_password(self, request, id, form_url=''):\r
1135         if not self.has_change_permission(request):\r
1136             raise PermissionDenied\r
1137         user = get_object_or_404(self.get_queryset(request), pk=id)\r
1138         if request.method == 'POST':\r
1139             form = self.change_password_form(user, request.POST)\r
1140             if form.is_valid():\r
1141                 form.save()\r
1142                 change_message = self.construct_change_message(request, form, None)\r
1143                 self.log_change(request, user, change_message)\r
1144                 msg = ugettext('Password changed successfully.')\r
1145                 messages.success(request, msg)\r
1146                 update_session_auth_hash(request, form.user)\r
1147                 return HttpResponseRedirect('..')\r
1148         else:\r
1149             form = self.change_password_form(user)\r
1150 \r
1151         fieldsets = [(None, {'fields': list(form.base_fields)})]\r
1152         adminForm = admin.helpers.AdminForm(form, fieldsets, {})\r
1153 \r
1154         context = {\r
1155             'title': _('Change password: %s') % escape(user.get_username()),\r
1156             'adminForm': adminForm,\r
1157             'form_url': form_url,\r
1158             'form': form,\r
1159             'is_popup': (IS_POPUP_VAR in request.POST or\r
1160                          IS_POPUP_VAR in request.GET),\r
1161             'add': True,\r
1162             'change': False,\r
1163             'has_delete_permission': False,\r
1164             'has_change_permission': True,\r
1165             'has_absolute_url': False,\r
1166             'opts': self.model._meta,\r
1167             'original': user,\r
1168             'save_as': False,\r
1169             'show_save': True,\r
1170         }\r
1171         context.update(admin.site.each_context())\r
1172         return TemplateResponse(request,\r
1173             self.change_user_password_template or\r
1174             'admin/auth/user/change_password.html',\r
1175             context, current_app=self.admin_site.name)\r
1176 \r
1177     def response_add(self, request, obj, post_url_continue=None):\r
1178         """\r
1179         Determines the HttpResponse for the add_view stage. It mostly defers to\r
1180         its superclass implementation but is customized because the User model\r
1181         has a slightly different workflow.\r
1182         """\r
1183         # We should allow further modification of the user just added i.e. the\r
1184         # 'Save' button should behave like the 'Save and continue editing'\r
1185         # button except in two scenarios:\r
1186         # * The user has pressed the 'Save and add another' button\r
1187         # * We are adding a user in a popup\r
1188         if '_addanother' not in request.POST and IS_POPUP_VAR not in request.POST:\r
1189             request.POST['_continue'] = 1\r
1190         return super(UserAdmin, self).response_add(request, obj,\r
1191                                                    post_url_continue)
1192
1193     # ------------------------------------------------------------------------
1194     # end stuff copied from ModelAdmin.UserAdmin
1195     # ------------------------------------------------------------------------
1196
1197
1198 class DashboardViewAdmin(PlanetStackBaseAdmin):
1199     fieldsets = [('Dashboard View Details',
1200                    {'fields': ['backend_status_text', 'name', 'url'],
1201                     'classes': ['suit-tab suit-tab-general']})
1202                ]
1203     readonly_fields = ('backend_status_text', )
1204
1205     suit_form_tabs =(('general','Dashboard View Details'),)
1206
1207 class ServiceResourceInline(PlStackTabularInline):
1208     model = ServiceResource
1209     extra = 0
1210
1211 class ServiceClassAdmin(PlanetStackBaseAdmin):
1212     list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1213     list_display_links = ('backend_status_icon', 'name', )
1214     inlines = [ServiceResourceInline]
1215
1216     user_readonly_fields = ['name', 'commitment', 'membershipFee']
1217     user_readonly_inlines = []
1218
1219 class ReservedResourceInline(PlStackTabularInline):
1220     model = ReservedResource
1221     extra = 0
1222     suit_classes = 'suit-tab suit-tab-reservedresources'
1223
1224     def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1225         field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1226
1227         if db_field.name == 'resource':
1228             # restrict resources to those that the slice's service class allows
1229             if request._slice is not None:
1230                 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1231                 if len(field.queryset) > 0:
1232                     field.initial = field.queryset.all()[0]
1233             else:\r
1234                 field.queryset = field.queryset.none()\r
1235         elif db_field.name == 'sliver':\r
1236             # restrict slivers to those that belong to the slice\r
1237             if request._slice is not None:\r
1238                 field.queryset = field.queryset.filter(slice = request._slice)
1239             else:
1240                 field.queryset = field.queryset.none()\r
1241 \r
1242         return field
1243
1244     def queryset(self, request):
1245         return ReservedResource.select_by_user(request.user)
1246
1247 class ReservationChangeForm(forms.ModelForm):
1248     class Meta:
1249         model = Reservation
1250         widgets = {
1251             'slice' : LinkedSelect
1252         }
1253
1254 class ReservationAddForm(forms.ModelForm):
1255     slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1256     refresh = forms.CharField(widget=forms.HiddenInput())
1257
1258     class Media:
1259        css = {'all': ('planetstack.css',)}   # .field-refresh { display: none; }
1260
1261     def clean_slice(self):
1262         slice = self.cleaned_data.get("slice")
1263         x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1264         if len(x) == 0:
1265             raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1266         return slice
1267
1268     class Meta:
1269         model = Reservation
1270         widgets = {
1271             'slice' : LinkedSelect
1272         }
1273
1274
1275 class ReservationAddRefreshForm(ReservationAddForm):
1276     """ This form is displayed when the Reservation Form receives an update
1277         from the Slice dropdown onChange handler. It doesn't validate the
1278         data and doesn't save the data. This will cause the form to be
1279         redrawn.
1280     """
1281
1282     """ don't validate anything other than slice """
1283     dont_validate_fields = ("startTime", "duration")
1284
1285     def full_clean(self):
1286         result = super(ReservationAddForm, self).full_clean()
1287
1288         for fieldname in self.dont_validate_fields:
1289             if fieldname in self._errors:
1290                 del self._errors[fieldname]
1291
1292         return result
1293
1294     """ don't save anything """
1295     def is_valid(self):
1296         return False
1297
1298 class ReservationAdmin(PlanetStackBaseAdmin):
1299     fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
1300     fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1301     readonly_fields = ('backend_status_text', )
1302     list_display = ('startTime', 'duration')
1303     form = ReservationAddForm
1304
1305     suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1306
1307     inlines = [ReservedResourceInline]
1308     user_readonly_fields = fieldList
1309
1310     def add_view(self, request, form_url='', extra_context=None):
1311         timezone.activate(request.user.timezone)
1312         request._refresh = False
1313         request._slice = None
1314         if request.method == 'POST':
1315             # "refresh" will be set to "1" if the form was submitted due to
1316             # a change in the Slice dropdown.
1317             if request.POST.get("refresh","1") == "1":
1318                 request._refresh = True
1319                 request.POST["refresh"] = "0"
1320
1321             # Keep track of the slice that was selected, so the
1322             # reservedResource inline can filter items for the slice.
1323             request._slice = request.POST.get("slice",None)
1324             if (request._slice is not None):
1325                 request._slice = Slice.objects.get(id=request._slice)
1326
1327         result =  super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1328         return result
1329
1330     def changelist_view(self, request, extra_context = None):
1331         timezone.activate(request.user.timezone)
1332         return super(ReservationAdmin, self).changelist_view(request, extra_context)
1333
1334     def get_form(self, request, obj=None, **kwargs):
1335         request._obj_ = obj
1336         if obj is not None:
1337             # For changes, set request._slice to the slice already set in the
1338             # object.
1339             request._slice = obj.slice
1340             self.form = ReservationChangeForm
1341         else:
1342             if getattr(request, "_refresh", False):
1343                 self.form = ReservationAddRefreshForm
1344             else:
1345                 self.form = ReservationAddForm
1346         return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1347
1348     def get_readonly_fields(self, request, obj=None):
1349         if (obj is not None):
1350             # Prevent slice from being changed after the reservation has been
1351             # created.
1352             return ['slice']
1353         else:
1354             return []
1355
1356     def queryset(self, request):
1357         return Reservation.select_by_user(request.user)
1358
1359 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1360     list_display = ("backend_status_icon", "name", )
1361     list_display_links = ('backend_status_icon', 'name', )
1362     user_readonly_fields = ['name']
1363     user_readonly_inlines = []
1364
1365 class RouterAdmin(PlanetStackBaseAdmin):
1366     list_display = ("backend_status_icon", "name", )
1367     list_display_links = ('backend_status_icon', 'name', )
1368     user_readonly_fields = ['name']
1369     user_readonly_inlines = []
1370
1371 class RouterInline(PlStackTabularInline):
1372     model = Router.networks.through
1373     extra = 0
1374     verbose_name_plural = "Routers"
1375     verbose_name = "Router"
1376     suit_classes = 'suit-tab suit-tab-routers'
1377
1378 class NetworkParameterInline(PlStackGenericTabularInline):
1379     model = NetworkParameter
1380     extra = 0
1381     verbose_name_plural = "Parameters"
1382     verbose_name = "Parameter"
1383     suit_classes = 'suit-tab suit-tab-netparams'
1384     fields = ['backend_status_icon', 'parameter', 'value']
1385     readonly_fields = ('backend_status_icon', )
1386
1387 class NetworkSliversInline(PlStackTabularInline):
1388     fields = ['backend_status_icon', 'network','sliver','ip']
1389     readonly_fields = ("backend_status_icon", "ip", )
1390     model = NetworkSliver
1391     selflink_fieldname = "sliver"
1392     extra = 0
1393     verbose_name_plural = "Slivers"
1394     verbose_name = "Sliver"
1395     suit_classes = 'suit-tab suit-tab-networkslivers'
1396
1397 class NetworkSlicesInline(PlStackTabularInline):
1398     model = NetworkSlice
1399     selflink_fieldname = "slice"
1400     extra = 0
1401     verbose_name_plural = "Slices"
1402     verbose_name = "Slice"
1403     suit_classes = 'suit-tab suit-tab-networkslices'
1404     fields = ['backend_status_icon', 'network','slice']
1405     readonly_fields = ('backend_status_icon', )
1406
1407 class NetworkAdmin(PlanetStackBaseAdmin):
1408     list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1409     list_display_links = ('backend_status_icon', 'name', )
1410     readonly_fields = ("subnet", )
1411
1412     inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1413
1414     fieldsets = [
1415         (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']}),]
1416
1417     readonly_fields = ('backend_status_text', )
1418     user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1419
1420     suit_form_tabs =(
1421         ('general','Network Details'),
1422         ('netparams', 'Parameters'),
1423         ('networkslivers','Slivers'),
1424         ('networkslices','Slices'),
1425         ('routers','Routers'),
1426     )
1427 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1428     list_display = ("backend_status_icon", "name", "guaranteedBandwidth", "visibility")
1429     list_display_links = ('backend_status_icon', 'name', )
1430     user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1431     user_readonly_inlines = []
1432
1433 class FlavorAdmin(PlanetStackBaseAdmin):
1434     list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1435     list_display_links = ("backend_status_icon", "name")
1436     user_readonly_fields = ("name", "flavor")
1437     fields = ("name", "description", "flavor", "order", "default")
1438
1439 # register a signal that caches the user's credentials when they log in
1440 def cache_credentials(sender, user, request, **kwds):
1441     auth = {'username': request.POST['username'],
1442             'password': request.POST['password']}
1443     request.session['auth'] = auth
1444 user_logged_in.connect(cache_credentials)
1445
1446 def dollar_field(fieldName, short_description):
1447     def newFunc(self, obj):
1448         try:
1449             x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1450         except:
1451             x=getattr(obj, fieldName, 0.0)
1452         return x
1453     newFunc.short_description = short_description
1454     return newFunc
1455
1456 def right_dollar_field(fieldName, short_description):
1457     def newFunc(self, obj):
1458         try:
1459             #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1460             x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1461         except:
1462             x=getattr(obj, fieldName, 0.0)
1463         return x
1464     newFunc.short_description = short_description
1465     newFunc.allow_tags = True
1466     return newFunc
1467
1468 class InvoiceChargeInline(PlStackTabularInline):
1469     model = Charge
1470     extra = 0
1471     verbose_name_plural = "Charges"
1472     verbose_name = "Charge"
1473     exclude = ['account']
1474     fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1475     readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1476     can_delete = False
1477     max_num = 0
1478
1479     dollar_amount = right_dollar_field("amount", "Amount")
1480
1481 class InvoiceAdmin(admin.ModelAdmin):
1482     list_display = ("date", "account")
1483
1484     inlines = [InvoiceChargeInline]
1485
1486     fields = ["date", "account", "dollar_amount"]
1487     readonly_fields = ["date", "account", "dollar_amount"]
1488
1489     dollar_amount = dollar_field("amount", "Amount")
1490
1491 class InvoiceInline(PlStackTabularInline):
1492     model = Invoice
1493     extra = 0
1494     verbose_name_plural = "Invoices"
1495     verbose_name = "Invoice"
1496     fields = ["date", "dollar_amount"]
1497     readonly_fields = ["date", "dollar_amount"]
1498     suit_classes = 'suit-tab suit-tab-accountinvoice'
1499     can_delete=False
1500     max_num=0
1501
1502     dollar_amount = right_dollar_field("amount", "Amount")
1503
1504 class PendingChargeInline(PlStackTabularInline):
1505     model = Charge
1506     extra = 0
1507     verbose_name_plural = "Charges"
1508     verbose_name = "Charge"
1509     exclude = ["invoice"]
1510     fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1511     readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1512     suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1513     can_delete=False
1514     max_num=0
1515
1516     def queryset(self, request):
1517         qs = super(PendingChargeInline, self).queryset(request)
1518         qs = qs.filter(state="pending")
1519         return qs
1520
1521     dollar_amount = right_dollar_field("amount", "Amount")
1522
1523 class PaymentInline(PlStackTabularInline):
1524     model=Payment
1525     extra = 1
1526     verbose_name_plural = "Payments"
1527     verbose_name = "Payment"
1528     fields = ["date", "dollar_amount"]
1529     readonly_fields = ["date", "dollar_amount"]
1530     suit_classes = 'suit-tab suit-tab-accountpayments'
1531     can_delete=False
1532     max_num=0
1533
1534     dollar_amount = right_dollar_field("amount", "Amount")
1535
1536 class AccountAdmin(admin.ModelAdmin):
1537     list_display = ("site", "balance_due")
1538
1539     inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1540
1541     fieldsets = [
1542         (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1543
1544     readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1545
1546     suit_form_tabs =(
1547         ('general','Account Details'),
1548         ('accountinvoice', 'Invoices'),
1549         ('accountpayments', 'Payments'),
1550         ('accountpendingcharges','Pending Charges'),
1551     )
1552
1553     dollar_balance_due = dollar_field("balance_due", "Balance Due")
1554     dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1555     dollar_total_payments = dollar_field("total_payments", "Total Payments")
1556
1557 # Now register the new UserAdmin...
1558 admin.site.register(User, UserAdmin)
1559 # ... and, since we're not using Django's builtin permissions,
1560 # unregister the Group model from admin.
1561 #admin.site.unregister(Group)
1562
1563 #Do not show django evolution in the admin interface
1564 from django_evolution.models import Version, Evolution
1565 #admin.site.unregister(Version)
1566 #admin.site.unregister(Evolution)
1567
1568
1569 # When debugging it is often easier to see all the classes, but for regular use 
1570 # only the top-levels should be displayed
1571 showAll = False
1572
1573 admin.site.register(Deployment, DeploymentAdmin)
1574 admin.site.register(Site, SiteAdmin)
1575 admin.site.register(Slice, SliceAdmin)
1576 admin.site.register(Service, ServiceAdmin)
1577 admin.site.register(Reservation, ReservationAdmin)
1578 admin.site.register(Network, NetworkAdmin)
1579 admin.site.register(Router, RouterAdmin)
1580 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1581 admin.site.register(Account, AccountAdmin)
1582 admin.site.register(Invoice, InvoiceAdmin)
1583
1584 if True:
1585     admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1586     admin.site.register(ServiceClass, ServiceClassAdmin)
1587     #admin.site.register(PlanetStack)
1588     admin.site.register(Tag, TagAdmin)
1589     admin.site.register(DeploymentRole)
1590     admin.site.register(SiteRole)
1591     admin.site.register(SliceRole)
1592     admin.site.register(PlanetStackRole)
1593     admin.site.register(Node, NodeAdmin)
1594     #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1595     #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1596     admin.site.register(Sliver, SliverAdmin)
1597     admin.site.register(Image, ImageAdmin)
1598     admin.site.register(DashboardView, DashboardViewAdmin)
1599     admin.site.register(Flavor, FlavorAdmin)
1600