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