call user.set_password() in PLUser.save() instead of PLUserAdmin.save()
[plstackapi.git] / plstackapi / core / admin.py
1 from plstackapi.core.models import Site
2 from plstackapi.core.models import *
3 from plstackapi.openstack.driver import OpenStackDriver
4 from plstackapi.openstack.client import OpenStackClient
5
6 from django.contrib import admin
7 from django.contrib.auth.models import Group
8 from django import forms
9 from django.utils.safestring import mark_safe
10 from django.contrib.auth.admin import UserAdmin
11 from django.contrib.admin.widgets import FilteredSelectMultiple
12 from django.contrib.auth.forms import ReadOnlyPasswordHashField
13 from django.contrib.auth.signals import user_logged_in 
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', 'name', 'slice', 'image', 'key', 'node', 'deploymentNetwork']
35     extra = 0
36
37 class SiteInline(admin.TabularInline):
38     model = Site
39     extra = 0
40
41 class SliceInline(admin.TabularInline):
42     model = Slice
43     extra = 0
44
45 class UserInline(admin.TabularInline):
46     model = PLUser
47     extra = 0
48
49 class RoleInline(admin.TabularInline):
50     model = Role
51     extra = 0 
52
53 class NodeInline(admin.TabularInline):
54     model = Node
55     extra = 0
56
57 class PlainTextWidget(forms.Widget):
58     def render(self, _name, value, attrs):
59         return mark_safe(value) if value is not None else ''
60
61 class PlanetStackBaseAdmin(admin.ModelAdmin):
62     save_on_top = False
63
64 class OSModelAdmin(PlanetStackBaseAdmin):
65     """Attach client connection to openstack on delete() and save()""" 
66
67     def save_model(self, request, obj, form, change):
68         client = OpenStackClient(tenant=request.user.site.login_base, **request.session.get('auth', {}))
69         obj.driver = OpenStackDriver(client=client)
70         obj.caller = request.user
71         obj.save()
72
73     def delete_model(self, request, obj):
74         client = OpenStackClient(tenant=request.user.site.login_base, **request.session.get('auth', {}))
75         obj.driver = OpenStackDriver(client=client)
76         obj.caller = request.user
77         obj.delete()
78
79 class RoleAdmin(OSModelAdmin):
80     fieldsets = [
81         ('Role', {'fields': ['role_type']})
82     ]
83     list_display = ('role_type',)
84
85 class DeploymentNetworkAdminForm(forms.ModelForm):
86     sites = forms.ModelMultipleChoiceField(
87         queryset=Site.objects.all(),
88         required=False,
89         widget=FilteredSelectMultiple(
90             verbose_name=('Sites'), is_stacked=False
91         )
92     )
93     class Meta:
94         model = DeploymentNetwork
95
96     def __init__(self, *args, **kwargs):
97         super(DeploymentNetworkAdminForm, self).__init__(*args, **kwargs)
98
99         if self.instance and self.instance.pk:
100             self.fields['sites'].initial = self.instance.sites.all()
101
102     def save(self, commit=True):
103         deploymentNetwork = super(DeploymentNetworkAdminForm, self).save(commit=False)
104         if commit:
105             deploymentNetwork.save()
106
107         if deploymentNetwork.pk:
108             deploymentNetwork.sites = self.cleaned_data['sites']
109             self.save_m2m()
110
111         return deploymentNetwork
112
113 class DeploymentNetworkAdmin(PlanetStackBaseAdmin):
114     form = DeploymentNetworkAdminForm
115     inlines = [NodeInline,]
116
117 class SiteAdmin(OSModelAdmin):
118     fieldsets = [
119         (None, {'fields': ['name', 'site_url', 'enabled', 'is_public', 'login_base']}),
120         ('Location', {'fields': ['latitude', 'longitude']}),
121         ('Deployment Networks', {'fields': ['deployments']})
122     ]
123     list_display = ('name', 'login_base','site_url', 'enabled')
124     filter_horizontal = ('deployments',)
125     inlines = [NodeInline,]
126     search_fields = ['name']
127
128 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
129     fieldsets = [
130         (None, {'fields': ['user', 'site', 'role']})
131     ]
132     list_display = ('user', 'site', 'role')
133
134     def save_model(self, request, obj, form, change):
135         # update openstack connection to use this site/tenant   
136         client = OpenStackClient(tenant=obj.site.login_base, **request.session.get('auth', {}))
137         obj.driver = OpenStackDriver(client=client)
138         obj.caller = request.user
139         obj.save()
140
141     def delete_model(self, request, obj):
142         # update openstack connection to use this site/tenant   
143         client = OpenStackClient(tenant=obj.site.login_base, **request.session.get('auth', {}))
144         obj.driver = OpenStackDriver(client=client)
145         obj.caller = request.user
146         obj.delete()
147
148 class KeyAdmin(OSModelAdmin):
149     fieldsets = [
150         ('Key', {'fields': ['name', 'key', 'type', 'blacklisted', 'user']})
151     ]
152     list_display = ['name', 'key', 'type', 'blacklisted', 'user']
153
154     def get_queryset(self, request):
155         # get keys user is allowed to see
156         qs = super(KeyAdmin, self).get_queryset(request)
157         if request.user.is_superuser:
158             return qs
159         # users can only see their own keys
160         return qs.filter(user=request.user)  
161         
162
163 class SliceAdmin(OSModelAdmin):
164     fields = ['name', 'site', 'instantiation', 'description', 'slice_url']
165     list_display = ('name', 'site','slice_url', 'instantiation')
166     inlines = [SliverInline]
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             client = OpenStackClient(tenant=obj.name, **request.session.get('auth', {}))
175             inline.model.driver = OpenStackDriver(client=client)
176             inline.model.caller = request.user
177             yield inline.get_formset(request, obj)
178
179     def get_queryset(self, request):
180         qs = super(SliceAdmin, self).get_queryset(request)
181         if request.user.is_superuser:
182             return qs
183         # users can only see slices at their site
184         return qs.filter(site=request.user.site) 
185
186 class SliceMembershipAdmin(PlanetStackBaseAdmin):
187     fieldsets = [
188         (None, {'fields': ['user', 'slice', 'role']})
189     ]
190     list_display = ('user', 'slice', 'role')
191
192     def save_model(self, request, obj, form, change):
193         # update openstack connection to use this slice/tenant
194         client = OpenStackClient(tenant=obj.slice.name, **request.session.get('auth', {}))
195         obj.driver = OpenStackDriver(client=client)
196         obj.caller = request.user
197         obj.save()
198
199     def delete_model(self, request, obj):
200         # update openstack connection to use this slice/tenant
201         client = OpenStackClient(tenant=obj.slice.name, **request.session.get('auth', {}))
202         obj.driver = OpenStackDriver(client=client)
203         obj.caller = request.user
204         obj.delete()
205
206 class SubnetAdmin(PlanetStackBaseAdmin):
207     fields = ['cidr', 'ip_version', 'start', 'end', 'slice']
208     list_display = ('slice','cidr', 'start', 'end', 'ip_version')
209
210     def save_model(self, request, obj, form, change):
211         # update openstack connection to use this subnet's slice/tenant
212         client = OpenStackClient(tenant=obj.slice.name, **request.session.get('auth', {}))
213         obj.driver = OpenStackDriver(client=client)
214         obj.caller = request.user
215         obj.save()
216
217     def delete_model(self, request, obj):
218         # update openstack connection to use this subnet's slice/tenant
219         client = OpenStackClient(tenant=obj.slice.name, **request.session.get('auth', {}))
220         obj.driver = OpenStackDriver(client=client)
221         obj.caller = request.user
222         obj.delete()
223
224 class ImageAdmin(admin.ModelAdmin):
225     fields = ['image_id', 'name', 'disk_format', 'container_format']
226
227 class NodeAdmin(admin.ModelAdmin):
228     list_display = ('name', 'site', 'deploymentNetwork')
229     list_filter = ('deploymentNetwork',)
230
231
232 class SliverForm(forms.ModelForm):
233     class Meta:
234         ip = forms.CharField(widget=PlainTextWidget)
235         model = Sliver
236         widgets = {
237             'ip': PlainTextWidget(),
238         }
239
240 class SliverAdmin(PlanetStackBaseAdmin):
241     form = SliverForm
242     fieldsets = [
243         ('Sliver', {'fields': ['ip', 'name', 'slice', 'image', 'key', 'node', 'deploymentNetwork']})
244     ]
245     list_display = ['ip', 'name', 'slice', 'image', 'key', 'node', 'deploymentNetwork']
246
247     def save_model(self, request, obj, form, change):
248         # update openstack connection to use this sliver's slice/tenant
249         client = OpenStackClient(tenant=obj.slice.name, **request.session.get('auth', {}))
250         obj.driver = OpenStackDriver(client=client)
251         obj.caller = request.user
252         obj.save()
253
254     def delete_model(self, request, obj):
255         # update openstack connection to use this sliver's slice/tenant
256         client = OpenStackClient(tenant=obj.slice.name, **request.session.get('auth', {}))
257         obj.driver = OpenStackDriver(client=client)
258         obj.caller = request.user
259         obj.delete()
260      
261
262 class UserCreationForm(forms.ModelForm):
263     """A form for creating new users. Includes all the required
264     fields, plus a repeated password."""
265     password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
266     password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
267
268     class Meta:
269         model = PLUser
270         fields = ('email', 'firstname', 'lastname', 'phone', 'site')
271
272     def clean_password2(self):
273         # Check that the two password entries match
274         password1 = self.cleaned_data.get("password1")
275         password2 = self.cleaned_data.get("password2")
276         if password1 and password2 and password1 != password2:
277             raise forms.ValidationError("Passwords don't match")
278         return password2
279
280     def save(self, commit=True):
281         # Save the provided password in hashed format
282         user = super(UserCreationForm, self).save(commit=False)
283         user.password = self.cleaned_data["password1"]
284         #user.set_password(self.cleaned_data["password1"])
285         if commit:
286             user.save()
287         return user
288
289
290 class UserChangeForm(forms.ModelForm):
291     """A form for updating users. Includes all the fields on
292     the user, but replaces the password field with admin's
293     password hash display field.
294     """
295     password = ReadOnlyPasswordHashField()
296
297     class Meta:
298         model = PLUser
299
300     def clean_password(self):
301         # Regardless of what the user provides, return the initial value.
302         # This is done here, rather than on the field, because the
303         # field does not have access to the initial value
304         return self.initial["password"]
305
306
307 class PLUserAdmin(UserAdmin, OSModelAdmin):
308     class Meta:
309         app_label = "core"
310
311     # The forms to add and change user instances
312     form = UserChangeForm
313     add_form = UserCreationForm
314
315     # The fields to be used in displaying the User model.
316     # These override the definitions on the base UserAdmin
317     # that reference specific fields on auth.User.
318     list_display = ('email', 'site', 'firstname', 'lastname', 'last_login')
319     list_filter = ('site',)
320     fieldsets = (
321         (None, {'fields': ('email', 'password')}),
322         ('Personal info', {'fields': ('firstname','lastname','phone','site')}),
323         #('Important dates', {'fields': ('last_login',)}),
324     )
325     add_fieldsets = (
326         (None, {
327             'classes': ('wide',),
328             'fields': ('email', 'firstname', 'lastname', 'phone', 'site', 'password1', 'password2')}
329         ),
330     )
331     search_fields = ('email',)
332     ordering = ('email',)
333     filter_horizontal = ()
334
335 # register a signal that caches the user's credentials when they log in
336 def cache_credentials(sender, user, request, **kwds):
337     auth = {'username': request.POST['username'],
338             'password': request.POST['password']}
339     request.session['auth'] = auth
340 user_logged_in.connect(cache_credentials)
341
342 # Now register the new UserAdmin...
343 admin.site.register(PLUser, PLUserAdmin)
344 # ... and, since we're not using Django's builtin permissions,
345 # unregister the Group model from admin.
346 admin.site.unregister(Group)
347
348 admin.site.register(Site, SiteAdmin)
349 admin.site.register(SitePrivilege, SitePrivilegeAdmin)
350 admin.site.register(Slice, SliceAdmin)
351 admin.site.register(SliceMembership, SliceMembershipAdmin)
352 admin.site.register(Subnet, SubnetAdmin)
353 admin.site.register(Image, ImageAdmin)
354 admin.site.register(Node, NodeAdmin)
355 admin.site.register(Sliver, SliverAdmin)
356 admin.site.register(Key, KeyAdmin)
357 admin.site.register(Role, RoleAdmin)
358 admin.site.register(DeploymentNetwork, DeploymentNetworkAdmin)
359