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