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