988a766cecf5e3650d2ee6f57330e9d835cb0bd4
[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 django.core.urlresolvers import reverse
16 from suit.widgets import LinkedSelect
17
18 import django_evolution 
19
20 class SingletonAdmin (admin.ModelAdmin):
21     def has_add_permission(self, request):
22         num_objects = self.model.objects.count()
23         if num_objects >= 1:
24             return False
25         else:
26             return True
27
28
29 class PlStackTabularInline(admin.TabularInline):
30     pass
31
32 class ReservationInline(PlStackTabularInline):
33     model = Reservation
34     extra = 0
35     suit_classes = 'suit-tab suit-tab-reservations'
36
37
38 class ReadonlyTabularInline(PlStackTabularInline):
39     can_delete = False
40     extra = 0
41     editable_fields = []
42
43     def get_readonly_fields(self, request, obj=None):
44         fields = []
45         for field in self.model._meta.get_all_field_names():
46             if (not field == 'id'):
47                 if (field not in self.editable_fields):
48                     fields.append(field)
49         return fields
50
51     def has_add_permission(self, request):
52         return False
53
54 class TagInline(generic.GenericTabularInline):
55     model = Tag
56     extra = 0
57     suit_classes = 'suit-tab suit-tab-tags'
58
59 class NetworkLookerUpper:
60     """ This is a callable that looks up a network name in a sliver and returns
61         the ip address for that network.
62     """
63
64     def __init__(self, name):
65         self.short_description = name
66         self.__name__ = name
67         self.network_name = name
68
69     def __call__(self, obj):
70         if obj is not None:
71             for nbs in obj.networksliver_set.all():
72                 if (nbs.network.name == self.network_name):
73                     return nbs.ip
74         return ""
75
76     def __str__(self):
77         return self.network_name
78
79 class SliverInline(PlStackTabularInline):
80     model = Sliver
81     fields = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']
82     extra = 0
83     readonly_fields = ['ip', 'instance_name']
84     suit_classes = 'suit-tab suit-tab-slivers'
85
86 # Note this is breaking in the admin.py when trying to use an inline to add a node/image 
87 #    def _declared_fieldsets(self):
88 #        # Return None so django will call get_fieldsets and we can insert our
89 #        # dynamic fields
90 #        return None
91 #
92 #    def get_readonly_fields(self, request, obj=None):
93 #        readonly_fields = super(SliverInline, self).get_readonly_fields(request, obj)
94 #
95 #        # Lookup the networks that are bound to the slivers, and add those
96 #        # network names to the list of readonly fields.
97 #
98 #        for sliver in obj.slivers.all():
99 #            for nbs in sliver.networksliver_set.all():
100 #                if nbs.ip:
101 #                    network_name = nbs.network.name
102 #                    if network_name not in [str(x) for x in readonly_fields]:
103 #                        readonly_fields.append(NetworkLookerUpper(network_name))
104 #
105 #        return readonly_fields
106 #
107 #    def get_fieldsets(self, request, obj=None):
108 #        form = self.get_formset(request, obj).form
109 #        # fields = the read/write files + the read-only fields
110 #        fields = self.fields
111 #        for fieldName in self.get_readonly_fields(request,obj):
112 #            if not fieldName in fields:
113 #                fields.append(fieldName)
114 #
115 #        return [(None, {'fields': fields})]
116
117     
118
119 class SiteInline(PlStackTabularInline):
120     model = Site
121     extra = 0
122     suit_classes = 'suit-tab suit-tab-sites'
123
124 class UserInline(PlStackTabularInline):
125     model = User
126     fields = ['email', 'firstname', 'lastname']
127     extra = 0
128     suit_classes = 'suit-tab suit-tab-users'
129
130 class SliceInline(PlStackTabularInline):
131     model = Slice
132     fields = ['name','site', 'serviceClass', 'service']
133     extra = 0
134     suit_classes = 'suit-tab suit-tab-slices'
135
136
137 class RoleInline(PlStackTabularInline):
138     model = Role
139     extra = 0 
140     suit_classes = 'suit-tab suit-tab-roles'
141
142 class NodeInline(PlStackTabularInline):
143     model = Node
144     extra = 0
145     suit_classes = 'suit-tab suit-tab-nodes'
146
147 class SlicePrivilegeInline(PlStackTabularInline):
148     model = SlicePrivilege
149     extra = 0
150     suit_classes = 'suit-tab suit-tab-sliceprivileges'
151
152 class DeploymentPrivilegeInline(PlStackTabularInline):
153     model = DeploymentPrivilege
154     extra = 0
155     suit_classes = 'suit-tab suit-tab-deploymentprivileges'
156
157 class SitePrivilegeInline(PlStackTabularInline):
158     model = SitePrivilege
159     extra = 0
160     suit_classes = 'suit-tab suit-tab-siteprivileges'
161
162     def formfield_for_foreignkey(self, db_field, request, **kwargs):
163         if db_field.name == 'site':
164             if not request.user.is_admin:
165                 # only show sites where user is an admin or pi
166                 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
167                 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
168                 login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
169                 sites = Site.objects.filter(login_base__in=login_bases)
170                 kwargs['queryset'] = sites
171
172         if db_field.name == 'user':
173             if not request.user.is_admin:
174                 # only show users from sites where caller has admin or pi role
175                 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
176                 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
177                 sites = [site_privilege.site for site_privilege in site_privileges]
178                 site_privileges = SitePrivilege.objects.filter(site__in=sites)
179                 emails = [site_privilege.user.email for site_privilege in site_privileges]
180                 users = User.objects.filter(email__in=emails)
181                 kwargs['queryset'] = users
182         return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
183
184 class SitePrivilegeInline(PlStackTabularInline):
185     model = SitePrivilege
186     suit_classes = 'suit-tab suit-tab-siteprivileges'
187     extra = 0
188     fields = ('user', 'site','role')
189
190 class SlicePrivilegeInline(PlStackTabularInline):
191     model = SlicePrivilege
192     suit_classes = 'suit-tab suit-tab-sliceprivileges'
193     extra = 0
194     fields = ('user', 'slice','role')
195
196     def formfield_for_foreignkey(self, db_field, request, **kwargs):
197         if db_field.name == 'slice':
198             if not request.user.is_admin:
199                 # only show slices at sites where caller has admin or pi role
200                 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
201                 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
202                 sites = [site_privilege.site for site_privilege in site_privileges]
203                 slices = Slice.objects.filter(site__in=sites)
204                 kwargs['queryset'] = slices 
205         if db_field.name == 'user':
206             if not request.user.is_admin:
207                 # only show users from sites where caller has admin or pi role
208                 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
209                 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
210                 sites = [site_privilege.site for site_privilege in site_privileges]
211                 site_privileges = SitePrivilege.objects.filter(site__in=sites)
212                 emails = [site_privilege.user.email for site_privilege in site_privileges]   
213                 users = User.objects.filter(email__in=emails) 
214                 kwargs['queryset'] = list(users)
215
216         return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
217
218 class SliceNetworkInline(PlStackTabularInline):
219     model = Network.slices.through
220     extra = 0
221     verbose_name = "Network Connection"
222     verbose_name_plural = "Network Connections"
223     suit_classes = 'suit-tab suit-tab-slicenetworks'
224
225 class SliceTagInline(PlStackTabularInline):
226     model = SliceTag
227     extra = 0
228
229 class PlainTextWidget(forms.HiddenInput):
230     input_type = 'hidden'
231
232     def render(self, name, value, attrs=None):
233         if value is None:
234             value = ''
235         return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
236
237 class PlanetStackBaseAdmin(admin.ModelAdmin):
238     save_on_top = False
239
240 class SliceRoleAdmin(PlanetStackBaseAdmin):
241     model = SliceRole
242     pass
243
244 class SiteRoleAdmin(PlanetStackBaseAdmin):
245     model = SiteRole
246     pass
247
248 class DeploymentAdminForm(forms.ModelForm):
249     sites = forms.ModelMultipleChoiceField(
250         queryset=Site.objects.all(),
251         required=False,
252         widget=FilteredSelectMultiple(
253             verbose_name=('Sites'), is_stacked=False
254         )
255     )
256     class Meta:
257         model = Deployment
258
259
260 class DeploymentAdmin(PlanetStackBaseAdmin):
261     form = DeploymentAdminForm
262     inlines = [DeploymentPrivilegeInline,NodeInline,TagInline]
263     fieldsets = [
264         (None, {'fields': ['sites'], 'classes':['suit-tab suit-tab-sites']}),]
265     suit_form_tabs =(('sites', 'Sites'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags'))
266
267 class ServiceAttrAsTabInline(PlStackTabularInline):
268     model = ServiceAttribute
269     fields = ['name','value']
270     extra = 0
271     suit_classes = 'suit-tab suit-tab-serviceattrs'
272
273 class ServiceAttributeInline(PlStackTabularInline):
274     model = ServiceAttribute
275     fields = ['name','value']
276     extra = 0
277
278 class ServiceAdmin(PlanetStackBaseAdmin):
279     list_display = ("name","enabled")
280     fieldsets = [(None, {'fields': ['name','enabled','description']})]
281     inlines = [ServiceAttributeInline,]
282
283 class SiteAdmin(PlanetStackBaseAdmin):
284     fieldsets = [
285         (None, {'fields': ['name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink', 'location'], 'classes':['suit-tab suit-tab-general']}),
286         ('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
287     ]
288     suit_form_tabs =(('general', 'Site Details'),
289         ('users','Users'),
290         ('siteprivileges','Privileges'),
291         ('deployments','Deployments'),
292         ('slices','Slices'),
293         ('nodes','Nodes'),
294         ('tags','Tags'),
295     )
296     readonly_fields = ['accountLink']
297     list_display = ('name', 'login_base','site_url', 'enabled')
298     filter_horizontal = ('deployments',)
299     inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline]
300     search_fields = ['name']
301
302     def queryset(self, request):
303         # admins can see all keys. Users can only see sites they belong to.
304         qs = super(SiteAdmin, self).queryset(request)
305         if not request.user.is_admin:
306             valid_sites = [request.user.site.login_base]
307             roles = request.user.get_roles()
308             for tenant_list in roles.values():
309                 valid_sites.extend(tenant_list)
310             qs = qs.filter(login_base__in=valid_sites)
311         return qs
312
313     def get_formsets(self, request, obj=None):
314         for inline in self.get_inline_instances(request, obj):
315             # hide MyInline in the add view
316             if obj is None:
317                 continue
318             if isinstance(inline, SliceInline):
319                 inline.model.caller = request.user
320             yield inline.get_formset(request, obj)
321
322     def get_formsets(self, request, obj=None):
323         for inline in self.get_inline_instances(request, obj):
324             # hide MyInline in the add view
325             if obj is None:
326                 continue
327             if isinstance(inline, SliverInline):
328                 inline.model.caller = request.user
329             yield inline.get_formset(request, obj)
330
331     def accountLink(self, obj):
332         link_obj = obj.accounts.all()
333         if link_obj:
334             reverse_path = "admin:core_account_change"
335             url = reverse(reverse_path, args =(link_obj[0].id,))
336             return "<a href='%s'>%s</a>" % (url, "view billing details")
337         else:
338             return "no billing data for this site"
339     accountLink.allow_tags = True
340     accountLink.short_description = "Billing"
341
342 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
343     fieldsets = [
344         (None, {'fields': ['user', 'site', 'role'], 'classes':['collapse']})
345     ]
346     list_display = ('user', 'site', 'role')
347
348     def formfield_for_foreignkey(self, db_field, request, **kwargs):
349         if db_field.name == 'site':
350             if not request.user.is_admin:
351                 # only show sites where user is an admin or pi
352                 sites = set()
353                 for site_privilege in SitePrivilege.objects.filer(user=request.user):
354                     if site_privilege.role.role_type in ['admin', 'pi']:
355                         sites.add(site_privilege.site)
356                 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
357
358         if db_field.name == 'user':
359             if not request.user.is_admin:
360                 # only show users from sites where caller has admin or pi role
361                 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
362                 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
363                 sites = [site_privilege.site for site_privilege in site_privileges]
364                 site_privileges = SitePrivilege.objects.filter(site__in=sites)
365                 emails = [site_privilege.user.email for site_privilege in site_privileges]
366                 users = User.objects.filter(email__in=emails)
367                 kwargs['queryset'] = users
368
369         return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
370
371     def queryset(self, request):
372         # admins can see all privileges. Users can only see privileges at sites
373         # where they have the admin role or pi role.
374         qs = super(SitePrivilegeAdmin, self).queryset(request)
375         if not request.user.is_admin:
376             roles = Role.objects.filter(role_type__in=['admin', 'pi'])
377             site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
378             login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
379             sites = Site.objects.filter(login_base__in=login_bases)
380             qs = qs.filter(site__in=sites)
381         return qs
382
383 class SliceForm(forms.ModelForm):
384     class Meta:
385         model = Slice
386         widgets = {
387             'service': LinkedSelect 
388         }
389
390 class SliceAdmin(PlanetStackBaseAdmin):
391     form = SliceForm
392     fieldsets = [('Slice Details', {'fields': ['name', 'site', 'serviceClass', 'enabled','description', 'service', 'slice_url'], 'classes':['suit-tab suit-tab-general']}),]
393     list_display = ('name', 'site','serviceClass', 'slice_url')
394     inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
395
396
397     #inlines = [SliverInline, SliceMembershipInline, TagInline, SliceTagInline, SliceNetworkInline]
398     suit_form_tabs =(('general', 'Slice Details'),
399         ('slicenetworks','Networks'),
400         ('sliceprivileges','Privileges'),
401         ('slivers','Slivers'),
402         ('tags','Tags'),
403         ('reservations','Reservations'),
404     )
405
406     def formfield_for_foreignkey(self, db_field, request, **kwargs):
407         if db_field.name == 'site':
408             if not request.user.is_admin:
409                 # only show sites where user is a pi or admin 
410                 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
411                 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
412                 login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
413                 sites = Site.objects.filter(login_base__in=login_bases)
414                 kwargs['queryset'] = sites
415
416         return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
417
418     def queryset(self, request):
419         # admins can see all keys. Users can only see slices they belong to.
420         qs = super(SliceAdmin, self).queryset(request)
421         if not request.user.is_admin:
422             valid_slices = []
423             roles = request.user.get_roles()
424             for tenant_list in roles.values():
425                 valid_slices.extend(tenant_list)
426             qs = qs.filter(name__in=valid_slices)
427         return qs
428
429     def get_formsets(self, request, obj=None):
430         for inline in self.get_inline_instances(request, obj):
431             # hide MyInline in the add view
432             if obj is None:
433                 continue
434             if isinstance(inline, SliverInline):
435                 inline.model.caller = request.user
436             yield inline.get_formset(request, obj)
437
438     def get_queryset(self, request):
439         qs = super(SliceAdmin, self).get_queryset(request)
440         if request.user.is_superuser:
441             return qs
442         # users can only see slices at their site
443         return qs.filter(site=request.user.site)
444
445     def save_model(self, request, obj, form, change):
446         # update openstack connection to use this site/tenant
447         obj.caller = request.user
448         obj.save() 
449
450 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
451     fieldsets = [
452         (None, {'fields': ['user', 'slice', 'role']})
453     ]
454     list_display = ('user', 'slice', 'role')
455
456     def formfield_for_foreignkey(self, db_field, request, **kwargs):
457         if db_field.name == 'slice':
458             if not request.user.is_admin:
459                 # only show slices at sites where caller has admin or pi role
460                 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
461                 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
462                 sites = [site_privilege.site for site_privilege in site_privileges]
463                 slices = Slice.objects.filter(site__in=sites)
464                 kwargs['queryset'] = slices
465         
466         if db_field.name == 'user':
467             if not request.user.is_admin:
468                 # only show users from sites where caller has admin or pi role
469                 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
470                 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
471                 sites = [site_privilege.site for site_privilege in site_privileges]
472                 site_privileges = SitePrivilege.objects.filter(site__in=sites)
473                 emails = [site_privilege.user.email for site_privilege in site_privileges]
474                 users = User.objects.filter(email__in=emails)
475                 kwargs['queryset'] = users
476
477         return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
478
479     def queryset(self, request):
480         # admins can see all memberships. Users can only see memberships of
481         # slices where they have the admin role.
482         qs = super(SlicePrivilegeAdmin, self).queryset(request)
483         if not request.user.is_admin:
484             roles = Role.objects.filter(role_type__in=['admin', 'pi'])
485             site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
486             login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
487             sites = Site.objects.filter(login_base__in=login_bases)
488             slices = Slice.objects.filter(site__in=sites)
489             qs = qs.filter(slice__in=slices)
490         return qs
491
492     def save_model(self, request, obj, form, change):
493         # update openstack connection to use this site/tenant
494         auth = request.session.get('auth', {})
495         auth['tenant'] = obj.slice.name
496         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
497         obj.save()
498
499     def delete_model(self, request, obj):
500         # update openstack connection to use this site/tenant
501         auth = request.session.get('auth', {})
502         auth['tenant'] = obj.slice.name
503         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
504         obj.delete()
505
506
507 class ImageAdmin(PlanetStackBaseAdmin):
508
509     fieldsets = [('Image Details', 
510                    {'fields': ['image_id', 'name', 'disk_format', 'container_format'], 
511                     'classes': ['suit-tab suit-tab-general']})
512                ]
513
514     suit_form_tabs =(('general','Image Details'),('slivers','Slivers'))
515
516     inlines = [SliverInline]
517
518 class NodeForm(forms.ModelForm):
519     class Meta:
520         widgets = {
521             'site': LinkedSelect,
522             'deployment': LinkedSelect
523         }
524
525 class NodeAdmin(admin.ModelAdmin):
526     form = NodeForm
527     list_display = ('name', 'site', 'deployment')
528     list_filter = ('deployment',)
529     inlines = [TagInline,SliverInline]
530     fieldsets = [('Node Details', {'fields': ['name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
531
532     suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
533
534
535 class SliverForm(forms.ModelForm):
536     class Meta:
537         model = Sliver
538         ip = forms.CharField(widget=PlainTextWidget)
539         instance_name = forms.CharField(widget=PlainTextWidget)
540         widgets = {
541             'ip': PlainTextWidget(),
542             'instance_name': PlainTextWidget(),
543             'slice': LinkedSelect,
544             'deploymentNetwork': LinkedSelect,
545             'node': LinkedSelect,
546             'image': LinkedSelect
547         }
548
549 class TagAdmin(admin.ModelAdmin):
550     list_display = ['service', 'name', 'value', 'content_type', 'content_object',]
551
552 class SliverAdmin(PlanetStackBaseAdmin):
553     form = SliverForm
554     fieldsets = [
555         ('Sliver Details', {'fields': ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
556     ]
557     list_display = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']
558
559     suit_form_tabs =(('general', 'Sliver Details'),
560         ('tags','Tags'),
561     )
562
563     inlines = [TagInline]
564
565     def formfield_for_foreignkey(self, db_field, request, **kwargs):
566         if db_field.name == 'slice':
567             if not request.user.is_admin:
568                 slices = set([sm.slice.name for sm in SlicePrivilege.objects.filter(user=request.user)]) 
569                 kwargs['queryset'] = Slice.objects.filter(name__in=list(slices))
570
571         return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
572
573     def queryset(self, request):
574         # admins can see all slivers. Users can only see slivers of 
575         # the slices they belong to.
576         qs = super(SliverAdmin, self).queryset(request)
577         if not request.user.is_admin:
578             tenants = []
579             roles = request.user.get_roles()
580             for tenant_list in roles.values():
581                 tenants.extend(tenant_list)
582             valid_slices = Slice.objects.filter(name__in=tenants)
583             qs = qs.filter(slice__in=valid_slices)
584         return qs
585
586     def get_formsets(self, request, obj=None):
587         # make some fields read only if we are updating an existing record
588         if obj == None:
589             #self.readonly_fields = ('ip', 'instance_name') 
590             self.readonly_fields = () 
591         else:
592             self.readonly_fields = () 
593             #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key') 
594
595         for inline in self.get_inline_instances(request, obj):
596             # hide MyInline in the add view
597             if obj is None:
598                 continue
599             # give inline object access to driver and caller
600             auth = request.session.get('auth', {})
601             auth['tenant'] = obj.name       # meed to connect using slice's tenant
602             inline.model.os_manager = OpenStackManager(auth=auth, caller=request.user)
603             yield inline.get_formset(request, obj)
604
605     def save_model(self, request, obj, form, change):
606         # update openstack connection to use this site/tenant
607         auth = request.session.get('auth', {})
608         auth['tenant'] = obj.slice.name
609         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
610         obj.creator = request.user
611         obj.save()
612
613     def delete_model(self, request, obj):
614         # update openstack connection to use this site/tenant
615         auth = request.session.get('auth', {})
616         auth['tenant'] = obj.slice.name
617         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
618         obj.delete()
619
620 class UserCreationForm(forms.ModelForm):
621     """A form for creating new users. Includes all the required
622     fields, plus a repeated password."""
623     password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
624     password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
625
626     class Meta:
627         model = User
628         fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
629
630     def clean_password2(self):
631         # Check that the two password entries match
632         password1 = self.cleaned_data.get("password1")
633         password2 = self.cleaned_data.get("password2")
634         if password1 and password2 and password1 != password2:
635             raise forms.ValidationError("Passwords don't match")
636         return password2
637
638     def save(self, commit=True):
639         # Save the provided password in hashed format
640         user = super(UserCreationForm, self).save(commit=False)
641         user.password = self.cleaned_data["password1"]
642         #user.set_password(self.cleaned_data["password1"])
643         if commit:
644             user.save()
645         return user
646
647
648 class UserChangeForm(forms.ModelForm):
649     """A form for updating users. Includes all the fields on
650     the user, but replaces the password field with admin's
651     password hash display field.
652     """
653     password = ReadOnlyPasswordHashField()
654
655     class Meta:
656         model = User
657
658     def clean_password(self):
659         # Regardless of what the user provides, return the initial value.
660         # This is done here, rather than on the field, because the
661         # field does not have access to the initial value
662         return self.initial["password"]
663
664
665 class UserAdmin(UserAdmin):
666     class Meta:
667         app_label = "core"
668
669     # The forms to add and change user instances
670     form = UserChangeForm
671     add_form = UserCreationForm
672
673     # The fields to be used in displaying the User model.
674     # These override the definitions on the base UserAdmin
675     # that reference specific fields on auth.User.
676     list_display = ('email', 'firstname', 'lastname', 'is_admin', 'last_login')
677     #list_display = ('email', 'username','firstname', 'lastname', 'is_admin', 'last_login')
678     list_filter = ()
679     inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline]
680     fieldsets = (
681         ('Login Details', {'fields': ('email', 'site','password', 'is_admin', 'public_key'), 'classes':['suit-tab suit-tab-general']}),
682         ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
683         #('Important dates', {'fields': ('last_login',)}),
684     )
685     add_fieldsets = (
686         (None, {
687             'classes': ('wide',),
688             'fields': ('email', 'firstname', 'lastname', 'phone', 'public_key','password1', 'password2')}
689         ),
690     )
691     search_fields = ('email',)
692     ordering = ('email',)
693     filter_horizontal = ()
694
695     suit_form_tabs =(('general','Login Details'),('contact','Contact Information'),('sliceprivileges','Slice Privileges'),('siteprivileges','Site Privileges'),('deploymentprivileges','Deployment Privileges'))
696
697     def formfield_for_foreignkey(self, db_field, request, **kwargs):
698         if db_field.name == 'site':
699             if not request.user.is_admin:
700                 # show sites where caller is an admin or pi 
701                 sites = []
702                 for site_privilege in SitePrivilege.objects.filer(user=request.user):
703                     if site_privilege.role.role_type in ['admin', 'pi']:
704                         sites.append(site_privilege.site.login_base)  
705                 kwargs['queryset'] = Site.objects.filter(login_base__in(list(sites)))
706
707         return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
708
709 class ServiceResourceInline(admin.TabularInline):
710     model = ServiceResource
711     extra = 0
712
713 class ServiceClassAdmin(admin.ModelAdmin):
714     list_display = ('name', 'commitment', 'membershipFee')
715     inlines = [ServiceResourceInline]
716
717 class ReservedResourceInline(admin.TabularInline):
718     model = ReservedResource
719     extra = 0
720     suit_classes = 'suit-tab suit-tab-reservedresources'
721
722     def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
723         field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
724
725         if db_field.name == 'resource':
726             # restrict resources to those that the slice's service class allows
727             if request._slice is not None:
728                 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
729                 if len(field.queryset) > 0:
730                     field.initial = field.queryset.all()[0]
731             else:\r
732                 field.queryset = field.queryset.none()\r
733         elif db_field.name == 'sliver':\r
734             # restrict slivers to those that belong to the slice\r
735             if request._slice is not None:\r
736                 field.queryset = field.queryset.filter(slice = request._slice)
737             else:
738                 field.queryset = field.queryset.none()\r
739 \r
740         return field
741
742 class ReservationChangeForm(forms.ModelForm):
743     class Meta:
744         model = Reservation
745         widgets = {
746             'slice' : LinkedSelect
747         }
748
749 class ReservationAddForm(forms.ModelForm):
750     slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
751     refresh = forms.CharField(widget=forms.HiddenInput())
752
753     class Media:
754        css = {'all': ('planetstack.css',)}   # .field-refresh { display: none; }
755
756     def clean_slice(self):
757         slice = self.cleaned_data.get("slice")
758         x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
759         if len(x) == 0:
760             raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
761         return slice
762
763     class Meta:
764         model = Reservation
765         widgets = {
766             'slice' : LinkedSelect
767         }
768
769
770 class ReservationAddRefreshForm(ReservationAddForm):
771     """ This form is displayed when the Reservation Form receives an update
772         from the Slice dropdown onChange handler. It doesn't validate the
773         data and doesn't save the data. This will cause the form to be
774         redrawn.
775     """
776
777     """ don't validate anything other than slice """
778     dont_validate_fields = ("startTime", "duration")
779
780     def full_clean(self):
781         result = super(ReservationAddForm, self).full_clean()
782
783         for fieldname in self.dont_validate_fields:
784             if fieldname in self._errors:
785                 del self._errors[fieldname]
786
787         return result
788
789     """ don't save anything """
790     def is_valid(self):
791         return False
792
793 class ReservationAdmin(admin.ModelAdmin):
794     fieldsets = [('Reservation Details', {'fields': ['slice', 'startTime', 'duration'], 'classes': ['suit-tab suit-tab-general']})]
795     list_display = ('startTime', 'duration')
796     form = ReservationAddForm
797
798     suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
799
800     inlines = [ReservedResourceInline]
801
802     def add_view(self, request, form_url='', extra_context=None):
803         timezone.activate(request.user.timezone)
804         request._refresh = False
805         request._slice = None
806         if request.method == 'POST':
807             # "refresh" will be set to "1" if the form was submitted due to
808             # a change in the Slice dropdown.
809             if request.POST.get("refresh","1") == "1":
810                 request._refresh = True
811                 request.POST["refresh"] = "0"
812
813             # Keep track of the slice that was selected, so the
814             # reservedResource inline can filter items for the slice.
815             request._slice = request.POST.get("slice",None)
816             if (request._slice is not None):
817                 request._slice = Slice.objects.get(id=request._slice)
818
819         result =  super(ReservationAdmin, self).add_view(request, form_url, extra_context)
820         return result
821
822     def changelist_view(self, request, extra_context = None):
823         timezone.activate(request.user.timezone)
824         return super(ReservationAdmin, self).changelist_view(request, extra_context)
825
826     def get_form(self, request, obj=None, **kwargs):
827         request._obj_ = obj
828         if obj is not None:
829             # For changes, set request._slice to the slice already set in the
830             # object.
831             request._slice = obj.slice
832             self.form = ReservationChangeForm
833         else:
834             if getattr(request, "_refresh", False):
835                 self.form = ReservationAddRefreshForm
836             else:
837                 self.form = ReservationAddForm
838         return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
839
840     def get_readonly_fields(self, request, obj=None):
841         if (obj is not None):
842             # Prevent slice from being changed after the reservation has been
843             # created.
844             return ['slice']
845         else:
846             return []
847
848 class NetworkParameterTypeAdmin(admin.ModelAdmin):
849     list_display = ("name", )
850
851 class RouterAdmin(admin.ModelAdmin):
852     list_display = ("name", )
853
854 class RouterInline(admin.TabularInline):
855     model = Router.networks.through
856     extra = 0
857     verbose_name_plural = "Routers"
858     verbose_name = "Router"
859     suit_classes = 'suit-tab suit-tab-routers'
860
861 class NetworkParameterInline(generic.GenericTabularInline):
862     model = NetworkParameter
863     extra = 1
864     verbose_name_plural = "Parameters"
865     verbose_name = "Parameter"
866     suit_classes = 'suit-tab suit-tab-netparams'
867
868 class NetworkSliversInline(admin.TabularInline):
869     readonly_fields = ("ip", )
870     model = NetworkSliver
871     extra = 0
872     verbose_name_plural = "Slivers"
873     verbose_name = "Sliver"
874     suit_classes = 'suit-tab suit-tab-networkslivers'
875
876 class NetworkSlicesInline(admin.TabularInline):
877     model = NetworkSlice
878     extra = 0
879     verbose_name_plural = "Slices"
880     verbose_name = "Slice"
881     suit_classes = 'suit-tab suit-tab-networkslices'
882
883 class NetworkForm(forms.ModelForm):
884     class Meta:
885         widgets = {
886             'deployment': LinkedSelect,
887             'site': LinkedSelect,
888         }
889
890 class NetworkAdmin(admin.ModelAdmin):
891     form = NetworkForm
892     list_display = ("name", "subnet", "ports", "labels")
893     list_filter = ('deployment', )
894     readonly_fields = ("subnet", )
895
896     inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
897
898     fieldsets = [
899         (None, {'fields': ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','site','deployment','network_id','router_id','subnet_id','subnet'], 'classes':['suit-tab suit-tab-general']}),]
900
901     suit_form_tabs =(
902         ('general','Network Details'),
903         ('netparams', 'Parameters'),
904         ('networkslivers','Slivers'),
905         ('networkslices','Slices'),
906         ('routers','Routers'),
907     )
908 class NetworkTemplateAdmin(admin.ModelAdmin):
909     list_display = ("name", "guaranteedBandwidth", "visibility")
910
911 # register a signal that caches the user's credentials when they log in
912 def cache_credentials(sender, user, request, **kwds):
913     auth = {'username': request.POST['username'],
914             'password': request.POST['password']}
915     request.session['auth'] = auth
916 user_logged_in.connect(cache_credentials)
917
918 def dollar_field(fieldName, short_description):
919     def newFunc(self, obj):
920         try:
921             x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
922         except:
923             x=getattr(obj, fieldName, 0.0)
924         return x
925     newFunc.short_description = short_description
926     return newFunc
927
928 def right_dollar_field(fieldName, short_description):
929     def newFunc(self, obj):
930         try:
931             #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
932             x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
933         except:
934             x=getattr(obj, fieldName, 0.0)
935         return x
936     newFunc.short_description = short_description
937     newFunc.allow_tags = True
938     return newFunc
939
940 class InvoiceChargeInline(admin.TabularInline):
941     model = Charge
942     extra = 0
943     verbose_name_plural = "Charges"
944     verbose_name = "Charge"
945     exclude = ['enacted', 'account']
946     fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
947     readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
948     can_delete = False
949     max_num = 0
950
951     dollar_amount = right_dollar_field("amount", "Amount")
952
953 class InvoiceAdmin(admin.ModelAdmin):
954     list_display = ("date", "account")
955
956     inlines = [InvoiceChargeInline]
957
958     fields = ["date", "account", "dollar_amount"]
959     readonly_fields = ["date", "account", "dollar_amount"]
960
961     dollar_amount = dollar_field("amount", "Amount")
962
963 class InvoiceInline(admin.TabularInline):
964     model = Invoice
965     extra = 0
966     verbose_name_plural = "Invoices"
967     verbose_name = "Invoice"
968     exclude = ['enacted']
969     fields = ["date", "dollar_amount", "invoiceLink"]
970     readonly_fields = ["date", "dollar_amount", "invoiceLink"]
971     suit_classes = 'suit-tab suit-tab-accountinvoice'
972     can_delete=False
973     max_num=0
974
975     dollar_amount = right_dollar_field("amount", "Amount")
976
977     def invoiceLink(self, obj):
978         reverse_path = "admin:core_invoice_change"
979         url = reverse(reverse_path, args =(obj.id,))
980         return "<a href='%s'>%s</a>" % (url, "details")
981     invoiceLink.allow_tags = True
982     invoiceLink.short_description = "Details"
983
984 class PendingChargeInline(admin.TabularInline):
985     model = Charge
986     extra = 0
987     verbose_name_plural = "Charges"
988     verbose_name = "Charge"
989     exclude = ['enacted', "invoice"]
990     fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
991     readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
992     suit_classes = 'suit-tab suit-tab-accountpendingcharges'
993     can_delete=False
994     max_num=0
995
996     def queryset(self, request):
997         qs = super(PendingChargeInline, self).queryset(request)
998         qs = qs.filter(state="pending")
999         return qs
1000
1001     dollar_amount = right_dollar_field("amount", "Amount")
1002
1003 class PaymentInline(admin.TabularInline):
1004     model=Payment
1005     extra = 1
1006     verbose_name_plural = "Payments"
1007     verbose_name = "Payment"
1008     exclude = ['enacted']
1009     fields = ["date", "dollar_amount"]
1010     readonly_fields = ["date", "dollar_amount"]
1011     suit_classes = 'suit-tab suit-tab-accountpayments'
1012     can_delete=False
1013     max_num=0
1014
1015     dollar_amount = right_dollar_field("amount", "Amount")
1016
1017
1018 class AccountAdmin(admin.ModelAdmin):
1019     list_display = ("site", "balance_due")
1020
1021     inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1022
1023     fieldsets = [
1024         (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']})] # ,'classes':['suit-tab suit-tab-general']}),]
1025
1026     readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1027
1028     suit_form_tabs =(
1029         ('general','Account Details'),
1030         ('accountinvoice', 'Invoices'),
1031         ('accountpayments', 'Payments'),
1032         ('accountpendingcharges','Pending Charges'),
1033     )
1034
1035     dollar_balance_due = dollar_field("balance_due", "Balance Due")
1036     dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1037     dollar_total_payments = dollar_field("total_payments", "Total Payments")
1038
1039
1040 # Now register the new UserAdmin...
1041 admin.site.register(User, UserAdmin)
1042 # ... and, since we're not using Django's builtin permissions,
1043 # unregister the Group model from admin.
1044 #admin.site.unregister(Group)
1045
1046 #Do not show django evolution in the admin interface
1047 from django_evolution.models import Version, Evolution
1048 admin.site.unregister(Version)
1049 admin.site.unregister(Evolution)
1050
1051
1052 # When debugging it is often easier to see all the classes, but for regular use 
1053 # only the top-levels should be displayed
1054 showAll = True
1055
1056 admin.site.register(Account, AccountAdmin)
1057 admin.site.register(Invoice, InvoiceAdmin)
1058
1059 admin.site.register(Deployment, DeploymentAdmin)
1060 admin.site.register(Site, SiteAdmin)
1061 admin.site.register(Slice, SliceAdmin)
1062 admin.site.register(ServiceClass, ServiceClassAdmin)
1063 admin.site.register(Service, ServiceAdmin)
1064 admin.site.register(Reservation, ReservationAdmin)
1065 admin.site.register(Network, NetworkAdmin)
1066 admin.site.register(Router, RouterAdmin)
1067 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1068 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1069
1070 if showAll:
1071     #admin.site.register(PlanetStack)
1072     admin.site.register(Tag, TagAdmin)
1073     admin.site.register(DeploymentRole)
1074     admin.site.register(SiteRole)
1075     admin.site.register(SliceRole)
1076     admin.site.register(PlanetStackRole)
1077     admin.site.register(Node, NodeAdmin)
1078     #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1079     #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1080     admin.site.register(Sliver, SliverAdmin)
1081     admin.site.register(Image, ImageAdmin)
1082