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