inject node_deployment info into change_form for slice admin
[plstackapi.git] / planetstack / core / admin.py
1 from core.models import Site
2 from core.models import *
3 from openstack.manager import OpenStackManager
4
5 from django.contrib import admin
6 from django.contrib.auth.models import Group
7 from django import forms
8 from django.utils.safestring import mark_safe
9 from django.contrib.auth.admin import UserAdmin
10 from django.contrib.admin.widgets import FilteredSelectMultiple
11 from django.contrib.auth.forms import ReadOnlyPasswordHashField
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 render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
764         #deployment_nodes = {}
765         #for node in Node.objects.all():
766         #    deployment_nodes[node.deployment.id] = get(deployment_nodes, node.deployment.id, []).append( (node.id, node.name) )
767
768         deployment_nodes = []
769         for node in Node.objects.all():
770             deployment_nodes.append( (node.deployment.id, node.id, node.name) )
771
772         context["deployment_nodes"] = deployment_nodes
773
774         return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
775
776     def formfield_for_foreignkey(self, db_field, request, **kwargs):
777         if db_field.name == 'site':
778             kwargs['queryset'] = Site.select_by_user(request.user)
779                 
780         return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
781
782     def queryset(self, request):
783         # admins can see all keys. Users can only see slices they belong to.
784         return Slice.select_by_user(request.user)
785
786     def get_formsets(self, request, obj=None):
787         for inline in self.get_inline_instances(request, obj):
788             # hide MyInline in the add view
789             if obj is None:
790                 continue
791             if isinstance(inline, SliverInline):
792                 inline.model.caller = request.user
793             yield inline.get_formset(request, obj)
794
795
796 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
797     fieldsets = [
798         (None, {'fields': ['user', 'slice', 'role']})
799     ]
800     list_display = ('user', 'slice', 'role')
801
802     user_readonly_fields = ['user', 'slice', 'role']
803     user_readonly_inlines = []
804
805     def formfield_for_foreignkey(self, db_field, request, **kwargs):
806         if db_field.name == 'slice':
807             kwargs['queryset'] = Slice.select_by_user(request.user)
808         
809         if db_field.name == 'user':
810             kwargs['queryset'] = User.select_by_user(request.user)
811
812         return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
813
814     def queryset(self, request):
815         # admins can see all memberships. Users can only see memberships of
816         # slices where they have the admin role.
817         return SlicePrivilege.select_by_user(request.user)
818
819     def save_model(self, request, obj, form, change):
820         # update openstack connection to use this site/tenant
821         auth = request.session.get('auth', {})
822         auth['tenant'] = obj.slice.name
823         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
824         obj.save()
825
826     def delete_model(self, request, obj):
827         # update openstack connection to use this site/tenant
828         auth = request.session.get('auth', {})
829         auth['tenant'] = obj.slice.name
830         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
831         obj.delete()
832
833
834 class ImageAdmin(PlanetStackBaseAdmin):
835
836     fieldsets = [('Image Details', 
837                    {'fields': ['name', 'disk_format', 'container_format'], 
838                     'classes': ['suit-tab suit-tab-general']})
839                ]
840
841     suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
842
843     inlines = [SliverInline, ImageDeploymentsInline]
844
845     user_readonly_fields = ['name', 'disk_format', 'container_format']
846     user_readonly_inlines = [SliverROInline, ImageDeploymentsROInline]
847     
848 class NodeForm(forms.ModelForm):
849     class Meta:
850         widgets = {
851             'site': LinkedSelect,
852             'deployment': LinkedSelect
853         }
854
855 class NodeAdmin(PlanetStackBaseAdmin):
856     form = NodeForm
857     list_display = ('name', 'site', 'deployment')
858     list_filter = ('deployment',)
859
860     inlines = [TagInline,SliverInline]
861     fieldsets = [('Node Details', {'fields': ['name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
862
863     user_readonly_fields = ['name','site','deployment']
864     user_readonly_inlines = [TagInline,SliverInline]
865
866     suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
867
868
869 class SliverForm(forms.ModelForm):
870     class Meta:
871         model = Sliver
872         ip = forms.CharField(widget=PlainTextWidget)
873         instance_name = forms.CharField(widget=PlainTextWidget)
874         widgets = {
875             'ip': PlainTextWidget(),
876             'instance_name': PlainTextWidget(),
877             'slice': LinkedSelect,
878             'deploymentNetwork': LinkedSelect,
879             'node': LinkedSelect,
880             'image': LinkedSelect
881         }
882
883 class TagAdmin(PlanetStackBaseAdmin):
884     list_display = ['service', 'name', 'value', 'content_type', 'content_object',]
885     user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
886     user_readonly_inlines = []
887
888 class SliverAdmin(PlanetStackBaseAdmin):
889     form = SliverForm
890     fieldsets = [
891         ('Sliver Details', {'fields': ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
892     ]
893     list_display = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']
894
895     suit_form_tabs =(('general', 'Sliver Details'),
896         ('tags','Tags'),
897     )
898
899     inlines = [TagInline]
900
901     user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image']
902     user_readonly_inlines = [TagROInline]
903
904     def formfield_for_foreignkey(self, db_field, request, **kwargs):
905         if db_field.name == 'slice':
906             kwargs['queryset'] = Slice.select_by_user(request.user)
907
908         return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
909
910     def queryset(self, request):
911         # admins can see all slivers. Users can only see slivers of 
912         # the slices they belong to.
913         return Sliver.select_by_user(request.user)
914
915
916     def get_formsets(self, request, obj=None):
917         # make some fields read only if we are updating an existing record
918         if obj == None:
919             #self.readonly_fields = ('ip', 'instance_name') 
920             self.readonly_fields = () 
921         else:
922             self.readonly_fields = () 
923             #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key') 
924
925         for inline in self.get_inline_instances(request, obj):
926             # hide MyInline in the add view
927             if obj is None:
928                 continue
929             if isinstance(inline, SliverInline):
930                 inline.model.caller = request.user
931             yield inline.get_formset(request, obj)
932
933     #def save_model(self, request, obj, form, change):
934     #    # update openstack connection to use this site/tenant
935     #    auth = request.session.get('auth', {})
936     #    auth['tenant'] = obj.slice.name
937     #    obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
938     #    obj.creator = request.user
939     #    obj.save()
940
941     #def delete_model(self, request, obj):
942     #    # update openstack connection to use this site/tenant
943     #    auth = request.session.get('auth', {})
944     #    auth['tenant'] = obj.slice.name
945     #    obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
946     #    obj.delete()
947
948 class UserCreationForm(forms.ModelForm):
949     """A form for creating new users. Includes all the required
950     fields, plus a repeated password."""
951     password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
952     password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
953
954     class Meta:
955         model = User
956         fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
957
958     def clean_password2(self):
959         # Check that the two password entries match
960         password1 = self.cleaned_data.get("password1")
961         password2 = self.cleaned_data.get("password2")
962         if password1 and password2 and password1 != password2:
963             raise forms.ValidationError("Passwords don't match")
964         return password2
965
966     def save(self, commit=True):
967         # Save the provided password in hashed format
968         user = super(UserCreationForm, self).save(commit=False)
969         user.password = self.cleaned_data["password1"]
970         #user.set_password(self.cleaned_data["password1"])
971         if commit:
972             user.save()
973         return user
974
975
976 class UserChangeForm(forms.ModelForm):
977     """A form for updating users. Includes all the fields on
978     the user, but replaces the password field with admin's
979     password hash display field.
980     """
981     password = ReadOnlyPasswordHashField(label='Password',
982                    help_text= '<a href=\"password/\">Change Password</a>.')
983
984     class Meta:
985         model = User
986
987     def clean_password(self):
988         # Regardless of what the user provides, return the initial value.
989         # This is done here, rather than on the field, because the
990         # field does not have access to the initial value
991         return self.initial["password"]
992
993 class UserDashboardViewInline(PlStackTabularInline):
994     model = UserDashboardView
995     extra = 0
996     suit_classes = 'suit-tab suit-tab-dashboards'
997     fields = ['user', 'dashboardView', 'order']
998
999 class UserDashboardViewROInline(ReadOnlyTabularInline):
1000     model = UserDashboardView
1001     extra = 0
1002     suit_classes = 'suit-tab suit-tab-dashboards'
1003     fields = ['user', 'dashboardView', 'order']
1004
1005 class UserAdmin(UserAdmin):
1006     class Meta:
1007         app_label = "core"
1008
1009     # The forms to add and change user instances
1010     form = UserChangeForm
1011     add_form = UserCreationForm
1012
1013     # The fields to be used in displaying the User model.
1014     # These override the definitions on the base UserAdmin
1015     # that reference specific fields on auth.User.
1016     list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
1017     #list_display = ('email', 'username','firstname', 'lastname', 'is_admin', 'last_login')
1018     list_filter = ('site',)
1019     inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
1020
1021     fieldListLoginDetails = ['email','site','password','is_readonly','is_amin','public_key']
1022     fieldListContactInfo = ['firstname','lastname','phone','timezone']
1023
1024     fieldsets = (
1025         ('Login Details', {'fields': ['email', 'site','password', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1026         ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1027         #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1028         #('Important dates', {'fields': ('last_login',)}),
1029     )
1030     add_fieldsets = (
1031         (None, {
1032             'classes': ('wide',),
1033             'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
1034         ),
1035     )
1036     search_fields = ('email',)
1037     ordering = ('email',)
1038     filter_horizontal = ()
1039
1040     user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1041     user_readonly_inlines = [SlicePrivilegeROInline,SitePrivilegeROInline,DeploymentPrivilegeROInline,UserDashboardViewROInline]
1042
1043     suit_form_tabs =(('general','Login Details'),
1044                      ('contact','Contact Information'),
1045                      ('sliceprivileges','Slice Privileges'),
1046                      ('siteprivileges','Site Privileges'),
1047                      ('deploymentprivileges','Deployment Privileges'),
1048                      ('dashboards','Dashboard Views'))
1049
1050     def formfield_for_foreignkey(self, db_field, request, **kwargs):
1051         if db_field.name == 'site':
1052             kwargs['queryset'] = Site.select_by_user(request.user)
1053
1054         return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1055
1056     def has_add_permission(self, request, obj=None):
1057         return (not self.__user_is_readonly(request))
1058
1059     def has_delete_permission(self, request, obj=None):
1060         return (not self.__user_is_readonly(request))
1061
1062     def get_actions(self,request):
1063         actions = super(UserAdmin,self).get_actions(request)
1064
1065         if self.__user_is_readonly(request):
1066             if 'delete_selected' in actions:
1067                 del actions['delete_selected']
1068
1069         return actions
1070
1071     def change_view(self,request,object_id, extra_context=None):
1072
1073         if self.__user_is_readonly(request):
1074             if not hasattr(self, "readonly_save"):
1075                 # save the original readonly fields\r
1076                 self.readonly_save = self.readonly_fields\r
1077                 self.inlines_save = self.inlines
1078             self.readonly_fields=self.user_readonly_fields
1079             self.inlines = self.user_readonly_inlines
1080         else:
1081             if hasattr(self, "readonly_save"):\r
1082                 # restore the original readonly fields\r
1083                 self.readonly_fields = self.readonly_save\r
1084                 self.inlines = self.inlines_save
1085
1086         try:
1087             return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1088         except PermissionDenied:
1089             pass
1090         if request.method == 'POST':
1091             raise PermissionDenied
1092         request.readonly = True
1093         return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1094
1095     def __user_is_readonly(self, request):
1096         #groups = [x.name for x in request.user.groups.all() ]
1097         #return "readonly" in groups
1098         return request.user.isReadOnlyUser()
1099
1100     def queryset(self, request):
1101         return User.select_by_user(request.user)
1102
1103 class DashboardViewAdmin(PlanetStackBaseAdmin):
1104     fieldsets = [('Dashboard View Details',
1105                    {'fields': ['name', 'url'],
1106                     'classes': ['suit-tab suit-tab-general']})
1107                ]
1108
1109     suit_form_tabs =(('general','Dashboard View Details'),)
1110
1111 class ServiceResourceROInline(ReadOnlyTabularInline):
1112     model = ServiceResource
1113     extra = 0
1114     fields = ['serviceClass', 'name', 'maxUnitsDeployment', 'maxUnitsNode', 'maxDuration', 'bucketInRate', 'bucketMaxSize', 'cost', 'calendarReservable']
1115
1116 class ServiceResourceInline(PlStackTabularInline):
1117     model = ServiceResource
1118     extra = 0
1119
1120 class ServiceClassAdmin(PlanetStackBaseAdmin):
1121     list_display = ('name', 'commitment', 'membershipFee')
1122     inlines = [ServiceResourceInline]
1123
1124     user_readonly_fields = ['name', 'commitment', 'membershipFee']
1125     user_readonly_inlines = []
1126
1127 class ReservedResourceROInline(ReadOnlyTabularInline):
1128     model = ReservedResource
1129     extra = 0
1130     fields = ['sliver', 'resource','quantity','reservationSet']
1131     suit_classes = 'suit-tab suit-tab-reservedresources'
1132
1133 class ReservedResourceInline(PlStackTabularInline):
1134     model = ReservedResource
1135     extra = 0
1136     suit_classes = 'suit-tab suit-tab-reservedresources'
1137
1138     def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1139         field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1140
1141         if db_field.name == 'resource':
1142             # restrict resources to those that the slice's service class allows
1143             if request._slice is not None:
1144                 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1145                 if len(field.queryset) > 0:
1146                     field.initial = field.queryset.all()[0]
1147             else:\r
1148                 field.queryset = field.queryset.none()\r
1149         elif db_field.name == 'sliver':\r
1150             # restrict slivers to those that belong to the slice\r
1151             if request._slice is not None:\r
1152                 field.queryset = field.queryset.filter(slice = request._slice)
1153             else:
1154                 field.queryset = field.queryset.none()\r
1155 \r
1156         return field
1157
1158     def queryset(self, request):
1159         return ReservedResource.select_by_user(request.user)
1160
1161 class ReservationChangeForm(forms.ModelForm):
1162     class Meta:
1163         model = Reservation
1164         widgets = {
1165             'slice' : LinkedSelect
1166         }
1167
1168 class ReservationAddForm(forms.ModelForm):
1169     slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1170     refresh = forms.CharField(widget=forms.HiddenInput())
1171
1172     class Media:
1173        css = {'all': ('planetstack.css',)}   # .field-refresh { display: none; }
1174
1175     def clean_slice(self):
1176         slice = self.cleaned_data.get("slice")
1177         x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1178         if len(x) == 0:
1179             raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1180         return slice
1181
1182     class Meta:
1183         model = Reservation
1184         widgets = {
1185             'slice' : LinkedSelect
1186         }
1187
1188
1189 class ReservationAddRefreshForm(ReservationAddForm):
1190     """ This form is displayed when the Reservation Form receives an update
1191         from the Slice dropdown onChange handler. It doesn't validate the
1192         data and doesn't save the data. This will cause the form to be
1193         redrawn.
1194     """
1195
1196     """ don't validate anything other than slice """
1197     dont_validate_fields = ("startTime", "duration")
1198
1199     def full_clean(self):
1200         result = super(ReservationAddForm, self).full_clean()
1201
1202         for fieldname in self.dont_validate_fields:
1203             if fieldname in self._errors:
1204                 del self._errors[fieldname]
1205
1206         return result
1207
1208     """ don't save anything """
1209     def is_valid(self):
1210         return False
1211
1212 class ReservationAdmin(PlanetStackBaseAdmin):
1213     fieldList = ['slice', 'startTime', 'duration']
1214     fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1215     list_display = ('startTime', 'duration')
1216     form = ReservationAddForm
1217
1218     suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1219
1220     inlines = [ReservedResourceInline]
1221     user_readonly_inlines = [ReservedResourceROInline]
1222     user_readonly_fields = fieldList
1223
1224     def add_view(self, request, form_url='', extra_context=None):
1225         timezone.activate(request.user.timezone)
1226         request._refresh = False
1227         request._slice = None
1228         if request.method == 'POST':
1229             # "refresh" will be set to "1" if the form was submitted due to
1230             # a change in the Slice dropdown.
1231             if request.POST.get("refresh","1") == "1":
1232                 request._refresh = True
1233                 request.POST["refresh"] = "0"
1234
1235             # Keep track of the slice that was selected, so the
1236             # reservedResource inline can filter items for the slice.
1237             request._slice = request.POST.get("slice",None)
1238             if (request._slice is not None):
1239                 request._slice = Slice.objects.get(id=request._slice)
1240
1241         result =  super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1242         return result
1243
1244     def changelist_view(self, request, extra_context = None):
1245         timezone.activate(request.user.timezone)
1246         return super(ReservationAdmin, self).changelist_view(request, extra_context)
1247
1248     def get_form(self, request, obj=None, **kwargs):
1249         request._obj_ = obj
1250         if obj is not None:
1251             # For changes, set request._slice to the slice already set in the
1252             # object.
1253             request._slice = obj.slice
1254             self.form = ReservationChangeForm
1255         else:
1256             if getattr(request, "_refresh", False):
1257                 self.form = ReservationAddRefreshForm
1258             else:
1259                 self.form = ReservationAddForm
1260         return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1261
1262     def get_readonly_fields(self, request, obj=None):
1263         if (obj is not None):
1264             # Prevent slice from being changed after the reservation has been
1265             # created.
1266             return ['slice']
1267         else:
1268             return []
1269
1270     def queryset(self, request):
1271         return Reservation.select_by_user(request.user)
1272
1273 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1274     list_display = ("name", )
1275     user_readonly_fields = ['name']
1276     user_readonly_inlines = []
1277
1278 class RouterAdmin(PlanetStackBaseAdmin):
1279     list_display = ("name", )
1280     user_readonly_fields = ['name']
1281     user_readonly_inlines = []
1282
1283 class RouterROInline(ReadOnlyTabularInline):
1284     model = Router.networks.through
1285     extra = 0
1286     verbose_name_plural = "Routers"
1287     verbose_name = "Router"
1288     suit_classes = 'suit-tab suit-tab-routers'
1289
1290     fields = ['name', 'owner', 'permittedNetworks', 'networks']
1291
1292 class RouterInline(PlStackTabularInline):
1293     model = Router.networks.through
1294     extra = 0
1295     verbose_name_plural = "Routers"
1296     verbose_name = "Router"
1297     suit_classes = 'suit-tab suit-tab-routers'
1298
1299 class NetworkParameterROInline(ReadOnlyTabularInline):
1300     model = NetworkParameter
1301     extra = 1
1302     verbose_name_plural = "Parameters"
1303     verbose_name = "Parameter"
1304     suit_classes = 'suit-tab suit-tab-netparams'
1305     fields = ['parameter', 'value', 'content_type', 'object_id', 'content_object']
1306
1307 class NetworkParameterInline(generic.GenericTabularInline):
1308     model = NetworkParameter
1309     extra = 1
1310     verbose_name_plural = "Parameters"
1311     verbose_name = "Parameter"
1312     suit_classes = 'suit-tab suit-tab-netparams'
1313
1314 class NetworkSliversROInline(ReadOnlyTabularInline):
1315     fields = ['network', 'sliver', 'ip', 'port_id']
1316     model = NetworkSliver
1317     extra = 0
1318     verbose_name_plural = "Slivers"
1319     verbose_name = "Sliver"
1320     suit_classes = 'suit-tab suit-tab-networkslivers'
1321
1322 class NetworkSliversInline(PlStackTabularInline):
1323     readonly_fields = ("ip", )
1324     model = NetworkSliver
1325     selflink_fieldname = "sliver"
1326     extra = 0
1327     verbose_name_plural = "Slivers"
1328     verbose_name = "Sliver"
1329     suit_classes = 'suit-tab suit-tab-networkslivers'
1330
1331 class NetworkSlicesROInline(ReadOnlyTabularInline):
1332     model = NetworkSlice
1333     extra = 0
1334     verbose_name_plural = "Slices"
1335     verbose_name = "Slice"
1336     suit_classes = 'suit-tab suit-tab-networkslices'
1337     fields = ['network','slice']
1338
1339 class NetworkSlicesInline(PlStackTabularInline):
1340     model = NetworkSlice
1341     selflink_fieldname = "slice"
1342     extra = 0
1343     verbose_name_plural = "Slices"
1344     verbose_name = "Slice"
1345     suit_classes = 'suit-tab suit-tab-networkslices'
1346
1347 class NetworkAdmin(PlanetStackBaseAdmin):
1348     list_display = ("name", "subnet", "ports", "labels")
1349     readonly_fields = ("subnet", )
1350
1351     inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1352
1353     fieldsets = [
1354         (None, {'fields': ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet'], 'classes':['suit-tab suit-tab-general']}),]
1355
1356     user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1357     user_readonly_inlines = [NetworkParameterROInline, NetworkSliversROInline, NetworkSlicesROInline, RouterROInline]
1358
1359     suit_form_tabs =(
1360         ('general','Network Details'),
1361         ('netparams', 'Parameters'),
1362         ('networkslivers','Slivers'),
1363         ('networkslices','Slices'),
1364         ('routers','Routers'),
1365     )
1366 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1367     list_display = ("name", "guaranteedBandwidth", "visibility")
1368     user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1369     user_readonly_inlines = []
1370
1371 # register a signal that caches the user's credentials when they log in
1372 def cache_credentials(sender, user, request, **kwds):
1373     auth = {'username': request.POST['username'],
1374             'password': request.POST['password']}
1375     request.session['auth'] = auth
1376 user_logged_in.connect(cache_credentials)
1377
1378 def dollar_field(fieldName, short_description):
1379     def newFunc(self, obj):
1380         try:
1381             x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1382         except:
1383             x=getattr(obj, fieldName, 0.0)
1384         return x
1385     newFunc.short_description = short_description
1386     return newFunc
1387
1388 def right_dollar_field(fieldName, short_description):
1389     def newFunc(self, obj):
1390         try:
1391             #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1392             x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1393         except:
1394             x=getattr(obj, fieldName, 0.0)
1395         return x
1396     newFunc.short_description = short_description
1397     newFunc.allow_tags = True
1398     return newFunc
1399
1400 class InvoiceChargeInline(PlStackTabularInline):
1401     model = Charge
1402     extra = 0
1403     verbose_name_plural = "Charges"
1404     verbose_name = "Charge"
1405     exclude = ['account']
1406     fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1407     readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1408     can_delete = False
1409     max_num = 0
1410
1411     dollar_amount = right_dollar_field("amount", "Amount")
1412
1413 class InvoiceAdmin(admin.ModelAdmin):
1414     list_display = ("date", "account")
1415
1416     inlines = [InvoiceChargeInline]
1417
1418     fields = ["date", "account", "dollar_amount"]
1419     readonly_fields = ["date", "account", "dollar_amount"]
1420
1421     dollar_amount = dollar_field("amount", "Amount")
1422
1423 class InvoiceInline(PlStackTabularInline):
1424     model = Invoice
1425     extra = 0
1426     verbose_name_plural = "Invoices"
1427     verbose_name = "Invoice"
1428     fields = ["date", "dollar_amount"]
1429     readonly_fields = ["date", "dollar_amount"]
1430     suit_classes = 'suit-tab suit-tab-accountinvoice'
1431     can_delete=False
1432     max_num=0
1433
1434     dollar_amount = right_dollar_field("amount", "Amount")
1435
1436 class PendingChargeInline(PlStackTabularInline):
1437     model = Charge
1438     extra = 0
1439     verbose_name_plural = "Charges"
1440     verbose_name = "Charge"
1441     exclude = ["invoice"]
1442     fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1443     readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1444     suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1445     can_delete=False
1446     max_num=0
1447
1448     def queryset(self, request):
1449         qs = super(PendingChargeInline, self).queryset(request)
1450         qs = qs.filter(state="pending")
1451         return qs
1452
1453     dollar_amount = right_dollar_field("amount", "Amount")
1454
1455 class PaymentInline(PlStackTabularInline):
1456     model=Payment
1457     extra = 1
1458     verbose_name_plural = "Payments"
1459     verbose_name = "Payment"
1460     fields = ["date", "dollar_amount"]
1461     readonly_fields = ["date", "dollar_amount"]
1462     suit_classes = 'suit-tab suit-tab-accountpayments'
1463     can_delete=False
1464     max_num=0
1465
1466     dollar_amount = right_dollar_field("amount", "Amount")
1467
1468 class AccountAdmin(admin.ModelAdmin):
1469     list_display = ("site", "balance_due")
1470
1471     inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1472
1473     fieldsets = [
1474         (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1475
1476     readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1477
1478     suit_form_tabs =(
1479         ('general','Account Details'),
1480         ('accountinvoice', 'Invoices'),
1481         ('accountpayments', 'Payments'),
1482         ('accountpendingcharges','Pending Charges'),
1483     )
1484
1485     dollar_balance_due = dollar_field("balance_due", "Balance Due")
1486     dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1487     dollar_total_payments = dollar_field("total_payments", "Total Payments")
1488
1489
1490 # Now register the new UserAdmin...
1491 admin.site.register(User, UserAdmin)
1492 # ... and, since we're not using Django's builtin permissions,
1493 # unregister the Group model from admin.
1494 #admin.site.unregister(Group)
1495
1496 #Do not show django evolution in the admin interface
1497 from django_evolution.models import Version, Evolution
1498 #admin.site.unregister(Version)
1499 #admin.site.unregister(Evolution)
1500
1501
1502 # When debugging it is often easier to see all the classes, but for regular use 
1503 # only the top-levels should be displayed
1504 showAll = False
1505
1506 admin.site.register(Deployment, DeploymentAdmin)
1507 admin.site.register(Site, SiteAdmin)
1508 admin.site.register(Slice, SliceAdmin)
1509 admin.site.register(Service, ServiceAdmin)
1510 admin.site.register(Reservation, ReservationAdmin)
1511 admin.site.register(Network, NetworkAdmin)
1512 admin.site.register(Router, RouterAdmin)
1513 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1514 admin.site.register(Account, AccountAdmin)
1515 admin.site.register(Invoice, InvoiceAdmin)
1516
1517 if True:
1518     admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1519     admin.site.register(ServiceClass, ServiceClassAdmin)
1520     #admin.site.register(PlanetStack)
1521     admin.site.register(Tag, TagAdmin)
1522     admin.site.register(DeploymentRole)
1523     admin.site.register(SiteRole)
1524     admin.site.register(SliceRole)
1525     admin.site.register(PlanetStackRole)
1526     admin.site.register(Node, NodeAdmin)
1527     #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1528     #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1529     admin.site.register(Sliver, SliverAdmin)
1530     admin.site.register(Image, ImageAdmin)
1531     admin.site.register(DashboardView, DashboardViewAdmin)
1532