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