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