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