bc49b8a8deeebfdb2198c6c371041d86e36ae066
[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
15
16 class ReadonlyTabularInline(admin.TabularInline):
17     can_delete = False
18     extra = 0
19     editable_fields = []
20
21     def get_readonly_fields(self, request, obj=None):
22         fields = []
23         for field in self.model._meta.get_all_field_names():
24             if (not field == 'id'):
25                 if (field not in self.editable_fields):
26                     fields.append(field)
27         return fields
28
29     def has_add_permission(self, request):
30         return False
31
32 class SliverInline(admin.TabularInline):
33     model = Sliver
34     fields = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'key', 'node', 'deploymentNetwork']
35     extra = 0
36     #readonly_fields = ['ip', 'instance_name', 'image']
37     readonly_fields = ['ip', 'instance_name']
38
39 class SiteInline(admin.TabularInline):
40     model = Site
41     extra = 0
42
43 class UserInline(admin.TabularInline):
44     model = User
45     fields = ['email', 'firstname', 'lastname']
46     extra = 0
47
48 class SliceInline(admin.TabularInline):
49     model = Slice
50     extra = 0
51
52 class RoleInline(admin.TabularInline):
53     model = Role
54     extra = 0 
55
56 class NodeInline(admin.TabularInline):
57     model = Node
58     extra = 0
59
60 class SitePrivilegeInline(admin.TabularInline):
61     model = SitePrivilege
62     extra = 0
63
64 class SliceMembershipInline(admin.TabularInline):
65     model = SliceMembership
66     extra = 0
67
68 class PlainTextWidget(forms.HiddenInput):
69     input_type = 'hidden'
70
71     def render(self, name, value, attrs=None):
72         if value is None:
73             value = ''
74         return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
75
76 class PlanetStackBaseAdmin(admin.ModelAdmin):
77     save_on_top = False
78
79 class OSModelAdmin(PlanetStackBaseAdmin):
80     """Attach client connection to openstack on delete() and save()"""
81
82     def save_model(self, request, obj, form, change):
83         if request.user.site:
84             auth = request.session.get('auth', {})
85             auth['tenant'] = request.user.site.login_base
86             obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
87         obj.save()
88
89     def delete_model(self, request, obj):
90         if request.user.site:
91             auth = request.session.get('auth', {})
92             auth['tenant'] = request.user.site.login_base
93             obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
94         obj.delete() 
95
96 class RoleAdmin(OSModelAdmin):
97     fieldsets = [
98         ('Role', {'fields': ['role_type']})
99     ]
100     list_display = ('role_type',)
101
102
103 class DeploymentNetworkAdminForm(forms.ModelForm):
104     sites = forms.ModelMultipleChoiceField(
105         queryset=Site.objects.all(),
106         required=False,
107         widget=FilteredSelectMultiple(
108             verbose_name=('Sites'), is_stacked=False
109         )
110     )
111     class Meta:
112         model = DeploymentNetwork
113
114     def __init__(self, *args, **kwargs):
115         super(DeploymentNetworkAdminForm, self).__init__(*args, **kwargs)
116
117         if self.instance and self.instance.pk:
118             self.fields['sites'].initial = self.instance.sites.all()
119
120     def save(self, commit=True):
121         deploymentNetwork = super(DeploymentNetworkAdminForm, self).save(commit=False)
122         if commit:
123             deploymentNetwork.save()
124
125         if deploymentNetwork.pk:
126             deploymentNetwork.sites = self.cleaned_data['sites']
127             self.save_m2m()
128
129         return deploymentNetwork
130
131 class DeploymentNetworkAdmin(PlanetStackBaseAdmin):
132     form = DeploymentNetworkAdminForm
133     inlines = [NodeInline,SliverInline]
134
135     def get_formsets(self, request, obj=None):
136         for inline in self.get_inline_instances(request, obj):
137             # hide MyInline in the add view
138             if obj is None:
139                 continue
140             # give inline object access to driver and caller
141             auth = request.session.get('auth', {})
142             if request.user.site:
143                 auth['tenant'] = request.user.site.login_base
144             inline.model.os_manager = OpenStackManager(auth=auth, caller=request.user)
145             yield inline.get_formset(request, obj)
146
147 class SiteAdmin(OSModelAdmin):
148     fieldsets = [
149         (None, {'fields': ['name', 'site_url', 'enabled', 'is_public', 'login_base']}),
150         ('Location', {'fields': ['latitude', 'longitude']}),
151         ('Deployment Networks', {'fields': ['deployments']})
152     ]
153     list_display = ('name', 'login_base','site_url', 'enabled')
154     filter_horizontal = ('deployments',)
155     inlines = [NodeInline, UserInline, SitePrivilegeInline]
156     search_fields = ['name']
157
158     def queryset(self, request):
159         # admins can see all keys. Users can only see sites they belong to.
160         qs = super(SiteAdmin, self).queryset(request)
161         if not request.user.is_admin:
162             valid_sites = [request.user.site.login_base]
163             roles = request.user.get_roles()
164             for tenant_list in roles.values():
165                 valid_sites.extend(tenant_list)
166             qs = qs.filter(login_base__in=valid_sites)
167         return qs
168
169     def get_formsets(self, request, obj=None):
170         for inline in self.get_inline_instances(request, obj):
171             # hide MyInline in the add view
172             if obj is None:
173                 continue
174             # give inline object access to driver and caller
175             auth = request.session.get('auth', {})
176             #auth['tenant'] = request.user.site.login_base
177             inline.model.os_manager = OpenStackManager(auth=auth, caller=request.user)
178             yield inline.get_formset(request, obj)
179
180 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
181     fieldsets = [
182         (None, {'fields': ['user', 'site', 'role']})
183     ]
184     list_display = ('user', 'site', 'role')
185
186     def queryset(self, request):
187         # admins can see all privileges. Users can only see privileges at sites
188         # where they have the admin role.
189         qs = super(SitePrivilegeAdmin, self).queryset(request)
190         if not request.user.is_admin:
191             roles = request.user.get_roles()
192             tenants = []
193             for (role, tenant_list) in roles:
194                 if role == 'admin':
195                     tenants.extend(tenant_list)
196             valid_sites = Sites.objects.filter(login_base__in=tenants)    
197             qs = qs.filter(site__in=valid_sites)
198         return qs
199
200     def save_model(self, request, obj, form, change):
201         # update openstack connection to use this site/tenant   
202         auth = request.session.get('auth', {})
203         #auth['tenant'] = obj.site.login_base
204         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
205         obj.save()
206
207     def delete_model(self, request, obj):
208         # update openstack connection to use this site/tenant   
209         auth = request.session.get('auth', {})
210         #auth['tenant'] = obj.site.login_base
211         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
212         obj.delete()
213
214 class KeyAdmin(OSModelAdmin):
215     fieldsets = [
216         ('Key', {'fields': ['key', 'type', 'blacklisted']})
217     ]
218     list_display = ['key', 'type', 'blacklisted']
219
220     #def queryset(self, request):
221         # admins can see all keys. Users can only see their own key.
222         #if request.user.is_admin:
223         #    qs = super(KeyAdmin, self).queryset(request) 
224         #else:
225         #    qs = Key.objects.filter(user=request.user)
226         #return qs
227
228 class SliceAdmin(OSModelAdmin):
229     fields = ['name', 'site', 'serviceClass', 'description', 'slice_url']
230     list_display = ('name', 'site','serviceClass', 'slice_url')
231     inlines = [SliverInline, SliceMembershipInline]
232
233     def queryset(self, request):
234         # admins can see all keys. Users can only see slices they belong to.
235         qs = super(SliceAdmin, self).queryset(request)
236         if not request.user.is_admin:
237             valid_slices = []
238             roles = request.user.get_roles()
239             for tenant_list in roles.values():
240                 valid_slices.extend(tenant_list)
241             qs = qs.filter(name__in=valid_slices)
242         return qs
243
244     def get_formsets(self, request, obj=None):
245         for inline in self.get_inline_instances(request, obj):
246             # hide MyInline in the add view
247             if obj is None:
248                 continue
249             # give inline object access to driver and caller
250             auth = request.session.get('auth', {})
251             auth['tenant'] = obj.name       # meed to connect using slice's tenant
252             inline.model.os_manager = OpenStackManager(auth=auth, caller=request.user)
253             yield inline.get_formset(request, obj)
254
255     def get_queryset(self, request):
256         qs = super(SliceAdmin, self).get_queryset(request)
257         if request.user.is_superuser:
258             return qs
259         # users can only see slices at their site
260         return qs.filter(site=request.user.site) 
261
262 class SliceMembershipAdmin(PlanetStackBaseAdmin):
263     fieldsets = [
264         (None, {'fields': ['user', 'slice', 'role']})
265     ]
266     list_display = ('user', 'slice', 'role')
267
268     def queryset(self, request):
269         # admins can see all memberships. Users can only see memberships of
270         # slices where they have the admin role.
271         qs = super(SliceMembershipAdmin, self).queryset(request)
272         if not request.user.is_admin:
273             roles = request.user.get_roles()
274             tenants = []
275             for (role, tenant_list) in roles:
276                 if role == 'admin':
277                     tenants.extend(tenant_list)
278             valid_slices = Slice.objects.filter(name__in=tenants)
279             qs = qs.filter(slice__in=valid_slices)
280         return qs
281
282     def save_model(self, request, obj, form, change):
283         # update openstack connection to use this site/tenant
284         auth = request.session.get('auth', {})
285         auth['tenant'] = obj.slice.name
286         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
287         obj.save()
288
289     def delete_model(self, request, obj):
290         # update openstack connection to use this site/tenant
291         auth = request.session.get('auth', {})
292         auth['tenant'] = obj.slice.name
293         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
294         obj.delete()
295
296
297 class ImageAdmin(admin.ModelAdmin):
298     fields = ['image_id', 'name', 'disk_format', 'container_format']
299
300 class NodeAdmin(admin.ModelAdmin):
301     list_display = ('name', 'site', 'deploymentNetwork')
302     list_filter = ('deploymentNetwork',)
303
304
305 class SliverForm(forms.ModelForm):
306     class Meta:
307         model = Sliver
308         ip = forms.CharField(widget=PlainTextWidget)
309         instance_name = forms.CharField(widget=PlainTextWidget)
310         widgets = {
311             'ip': PlainTextWidget(),
312             'instance_name': PlainTextWidget(),
313         }
314
315 class SliverAdmin(PlanetStackBaseAdmin):
316     form = SliverForm
317     fieldsets = [
318         ('Sliver', {'fields': ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'key', 'node', 'deploymentNetwork']})
319     ]
320     list_display = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'key', 'node', 'deploymentNetwork']
321
322     def queryset(self, request):
323         # admins can see all slivers. Users can only see slivers of 
324         # the slices they belong to.
325         qs = super(SliverAdmin, self).queryset(request)
326         if not request.user.is_admin:
327             tenants = []
328             roles = request.user.get_roles()
329             for tenant_list in roles.values():
330                 tenants.extend(tenant_list)
331             valid_slices = Slice.objects.filter(name__in=tenants)
332             qs = qs.filter(slice__in=valid_slices)
333         return qs
334
335     def get_formsets(self, request, obj=None):
336         # make some fields read only if we are updating an existing record
337         if obj == None:
338             #self.readonly_fields = ('ip', 'instance_name') 
339             self.readonly_fields = () 
340         else:
341             self.readonly_fields = () 
342             #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key') 
343
344         for inline in self.get_inline_instances(request, obj):
345             # hide MyInline in the add view
346             if obj is None:
347                 continue
348             # give inline object access to driver and caller
349             auth = request.session.get('auth', {})
350             auth['tenant'] = obj.name       # meed to connect using slice's tenant
351             inline.model.os_manager = OpenStackManager(auth=auth, caller=request.user)
352             yield inline.get_formset(request, obj)
353
354     def save_model(self, request, obj, form, change):
355         # update openstack connection to use this site/tenant
356         auth = request.session.get('auth', {})
357         auth['tenant'] = obj.slice.name
358         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
359         obj.save()
360
361     def delete_model(self, request, obj):
362         # update openstack connection to use this site/tenant
363         auth = request.session.get('auth', {})
364         auth['tenant'] = obj.slice.name
365         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
366         obj.delete()
367
368 class UserCreationForm(forms.ModelForm):
369     """A form for creating new users. Includes all the required
370     fields, plus a repeated password."""
371     password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
372     password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
373
374     class Meta:
375         model = User
376         fields = ('email', 'firstname', 'lastname', 'phone', 'key', 'site')
377
378     def clean_password2(self):
379         # Check that the two password entries match
380         password1 = self.cleaned_data.get("password1")
381         password2 = self.cleaned_data.get("password2")
382         if password1 and password2 and password1 != password2:
383             raise forms.ValidationError("Passwords don't match")
384         return password2
385
386     def save(self, commit=True):
387         # Save the provided password in hashed format
388         user = super(UserCreationForm, self).save(commit=False)
389         user.password = self.cleaned_data["password1"]
390         #user.set_password(self.cleaned_data["password1"])
391         if commit:
392             user.save()
393         return user
394
395
396 class UserChangeForm(forms.ModelForm):
397     """A form for updating users. Includes all the fields on
398     the user, but replaces the password field with admin's
399     password hash display field.
400     """
401     password = ReadOnlyPasswordHashField()
402
403     class Meta:
404         model = User
405
406     def clean_password(self):
407         # Regardless of what the user provides, return the initial value.
408         # This is done here, rather than on the field, because the
409         # field does not have access to the initial value
410         return self.initial["password"]
411
412
413 class UserAdmin(UserAdmin, OSModelAdmin):
414     class Meta:
415         app_label = "core"
416
417     # The forms to add and change user instances
418     form = UserChangeForm
419     add_form = UserCreationForm
420
421     # The fields to be used in displaying the User model.
422     # These override the definitions on the base UserAdmin
423     # that reference specific fields on auth.User.
424     list_display = ('email', 'site', 'firstname', 'lastname', 'is_admin', 'last_login')
425     list_filter = ('site',)
426     inlines = [SitePrivilegeInline, SliceMembershipInline]
427     fieldsets = (
428         (None, {'fields': ('email', 'password', 'site', 'is_admin', 'timezone')}),
429         ('Personal info', {'fields': ('firstname','lastname','phone', 'key')}),
430         #('Important dates', {'fields': ('last_login',)}),
431     )
432     add_fieldsets = (
433         (None, {
434             'classes': ('wide',),
435             'fields': ('email', 'firstname', 'lastname', 'phone', 'site', 'key','password1', 'password2', 'is_admin')}
436         ),
437     )
438     search_fields = ('email',)
439     ordering = ('email',)
440     filter_horizontal = ()
441
442 class ServiceResourceInline(admin.TabularInline):
443     model = ServiceResource
444     extra = 0
445
446 class ServiceClassAdmin(admin.ModelAdmin):
447     list_display = ('name', 'commitment', 'membershipFee')
448     inlines = [ServiceResourceInline]
449
450 class ReservedResourceInline(admin.TabularInline):
451     model = ReservedResource
452     extra = 0
453
454     def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
455         field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
456
457         if db_field.name == 'resource':
458             # restrict resources to those that the slice's service class allows
459             if request._slice is not None:
460                 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
461                 if len(field.queryset) > 0:
462                     field.initial = field.queryset.all()[0]
463             else:\r
464                 field.queryset = field.queryset.none()\r
465         elif db_field.name == 'sliver':\r
466             # restrict slivers to those that belong to the slice\r
467             if request._slice is not None:\r
468                 field.queryset = field.queryset.filter(slice = request._slice)
469             else:
470                 field.queryset = field.queryset.none()\r
471 \r
472         return field
473
474 class ReservationChangeForm(forms.ModelForm):
475     class Meta:
476         model = Reservation
477
478 class ReservationAddForm(forms.ModelForm):
479     slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
480     refresh = forms.CharField(widget=forms.HiddenInput())
481
482     class Media:
483        css = {'all': ('planetstack.css',)}   # .field-refresh { display: none; }
484
485     def clean_slice(self):
486         slice = self.cleaned_data.get("slice")
487         x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
488         if len(x) == 0:
489             raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
490         return slice
491
492     class Meta:
493         model = Reservation
494
495 class ReservationAddRefreshForm(ReservationAddForm):
496     """ This form is displayed when the Reservation Form receives an update
497         from the Slice dropdown onChange handler. It doesn't validate the
498         data and doesn't save the data. This will cause the form to be
499         redrawn.
500     """
501
502     """ don't validate anything other than slice """
503     dont_validate_fields = ("startTime", "duration")
504
505     def full_clean(self):
506         result = super(ReservationAddForm, self).full_clean()
507
508         for fieldname in self.dont_validate_fields:
509             if fieldname in self._errors:
510                 del self._errors[fieldname]
511
512         return result
513
514     """ don't save anything """
515     def is_valid(self):
516         return False
517
518 class ReservationAdmin(admin.ModelAdmin):
519     list_display = ('startTime', 'duration')
520     inlines = [ReservedResourceInline]
521     form = ReservationAddForm
522
523     def add_view(self, request, form_url='', extra_context=None):
524         timezone.activate(request.user.timezone)
525         request._refresh = False
526         request._slice = None
527         if request.method == 'POST':
528             # "refresh" will be set to "1" if the form was submitted due to
529             # a change in the Slice dropdown.
530             if request.POST.get("refresh","1") == "1":
531                 request._refresh = True
532                 request.POST["refresh"] = "0"
533
534             # Keep track of the slice that was selected, so the
535             # reservedResource inline can filter items for the slice.
536             request._slice = request.POST.get("slice",None)
537             if (request._slice is not None):
538                 request._slice = Slice.objects.get(id=request._slice)
539
540         result =  super(ReservationAdmin, self).add_view(request, form_url, extra_context)
541         return result
542
543     def changelist_view(self, request, extra_context = None):
544         timezone.activate(request.user.timezone)
545         return super(ReservationAdmin, self).changelist_view(request, extra_context)
546
547     def get_form(self, request, obj=None, **kwargs):
548         request._obj_ = obj\r
549         if obj is not None:\r
550             # For changes, set request._slice to the slice already set in the\r
551             # object.\r
552             request._slice = obj.slice\r
553             self.form = ReservationChangeForm\r
554         else:\r
555             if getattr(request, "_refresh", False):\r
556                 self.form = ReservationAddRefreshForm\r
557             else:\r
558                 self.form = ReservationAddForm\r
559         return super(ReservationAdmin, self).get_form(request, obj, **kwargs)\r
560 \r
561     def get_readonly_fields(self, request, obj=None):
562         if (obj is not None):\r
563             # Prevent slice from being changed after the reservation has been\r
564             # created.\r
565             return ['slice']\r
566         else:\r
567             return []
568
569 # register a signal that caches the user's credentials when they log in
570 def cache_credentials(sender, user, request, **kwds):
571     auth = {'username': request.POST['username'],
572             'password': request.POST['password']}
573     request.session['auth'] = auth
574 user_logged_in.connect(cache_credentials)
575
576 # Now register the new UserAdmin...
577 admin.site.register(User, UserAdmin)
578 # ... and, since we're not using Django's builtin permissions,
579 # unregister the Group model from admin.
580 admin.site.unregister(Group)
581
582 admin.site.register(Site, SiteAdmin)
583 admin.site.register(SitePrivilege, SitePrivilegeAdmin)
584 admin.site.register(Slice, SliceAdmin)
585 admin.site.register(SliceMembership, SliceMembershipAdmin)
586 #admin.site.register(Subnet)
587 admin.site.register(Image, ImageAdmin)
588 admin.site.register(Node, NodeAdmin)
589 admin.site.register(Sliver, SliverAdmin)
590 admin.site.register(Key, KeyAdmin)
591 admin.site.register(Role, RoleAdmin)
592 admin.site.register(DeploymentNetwork, DeploymentNetworkAdmin)
593 admin.site.register(ServiceClass, ServiceClassAdmin)
594 admin.site.register(Reservation, ReservationAdmin)
595