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