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