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