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