From acd6c602f2757246fc988a6c2e780399af2a70a3 Mon Sep 17 00:00:00 2001 From: Scott Baker Date: Fri, 3 Oct 2014 13:10:47 -0700 Subject: [PATCH] cleaner approach to dealing with UserAdmin, using multiple inheritance --- planetstack/core/admin.py | 157 +++----------------------------------- 1 file changed, 12 insertions(+), 145 deletions(-) diff --git a/planetstack/core/admin.py b/planetstack/core/admin.py index f4d6f8f..11a2c59 100644 --- a/planetstack/core/admin.py +++ b/planetstack/core/admin.py @@ -16,14 +16,6 @@ from suit.widgets import LinkedSelect from django.core.exceptions import PermissionDenied from django.core.urlresolvers import reverse, NoReverseMatch -# this block of stuff is needed for UserAdmin -from django.db import transaction -from django.utils.decorators import method_decorator -from django.views.decorators.csrf import csrf_protect -from django.views.decorators.debug import sensitive_post_parameters -csrf_protect_m = method_decorator(csrf_protect) -sensitive_post_parameters_m = method_decorator(sensitive_post_parameters()) - import django_evolution def backend_icon(obj): # backend_status, enacted, updated): @@ -51,7 +43,7 @@ class PlainTextWidget(forms.HiddenInput): value = '' return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs)) -class PermissionCheckingAdmin(admin.ModelAdmin): +class PermissionCheckingAdminMixin(object): # call save_by_user and delete_by_user instead of save and delete def has_add_permission(self, request, obj=None): @@ -97,7 +89,7 @@ class PermissionCheckingAdmin(admin.ModelAdmin): formset.save_m2m() def get_actions(self,request): - actions = super(PermissionCheckingAdmin,self).get_actions(request) + actions = super(PermissionCheckingAdminMixin,self).get_actions(request) if self.__user_is_readonly(request): if 'delete_selected' in actions: @@ -123,13 +115,13 @@ class PermissionCheckingAdmin(admin.ModelAdmin): self.inlines = self.inlines_save try: - return super(PermissionCheckingAdmin, self).change_view(request, object_id, extra_context=extra_context) + return super(PermissionCheckingAdminMixin, self).change_view(request, object_id, extra_context=extra_context) except PermissionDenied: pass if request.method == 'POST': raise PermissionDenied request.readonly = True - return super(PermissionCheckingAdmin, self).change_view(request, object_id, extra_context=extra_context) + return super(PermissionCheckingAdminMixin, self).change_view(request, object_id, extra_context=extra_context) def __user_is_readonly(self, request): return request.user.isReadOnlyUser() @@ -141,7 +133,10 @@ class PermissionCheckingAdmin(admin.ModelAdmin): return mark_safe(backend_icon(obj)) backend_status_icon.short_description = "" -class ReadOnlyAwareAdmin(PermissionCheckingAdmin): +class ReadOnlyAwareAdmin(PermissionCheckingAdminMixin, admin.ModelAdmin): + # Note: Make sure PermissionCheckingAdminMixin is listed before + # admin.ModelAdmin in the class declaration. + pass class PlanetStackBaseAdmin(ReadOnlyAwareAdmin): @@ -1011,17 +1006,16 @@ class UserDashboardViewInline(PlStackTabularInline): suit_classes = 'suit-tab suit-tab-dashboards' fields = ['user', 'dashboardView', 'order'] -class UserAdmin(PlanetStackBaseAdmin): +class UserAdmin(PermissionCheckingAdminMixin, UserAdmin): + # Note: Make sure PermissionCheckingAdminMixin is listed before + # admin.ModelAdmin in the class declaration. + class Meta: app_label = "core" - add_form_template = 'admin/auth/user/add_form.html' - change_user_password_template = None - # The forms to add and change user instances form = UserChangeForm add_form = UserCreationForm - change_password_form = AdminPasswordChangeForm # The fields to be used in displaying the User model. # These override the definitions on the base UserAdmin @@ -1068,133 +1062,6 @@ class UserAdmin(PlanetStackBaseAdmin): def queryset(self, request): return User.select_by_user(request.user) - # ------------------------------------------------------------------------ - # stuff copied from ModelAdmin.UserAdmin - # ------------------------------------------------------------------------ - def get_fieldsets(self, request, obj=None): - if not obj: - return self.add_fieldsets - return super(UserAdmin, self).get_fieldsets(request, obj) - - def get_form(self, request, obj=None, **kwargs): - """ - Use special form during user creation - """ - defaults = {} - if obj is None: - defaults['form'] = self.add_form - defaults.update(kwargs) - return super(UserAdmin, self).get_form(request, obj, **defaults) - - def get_urls(self): - from django.conf.urls import patterns - return patterns('', - (r'^(\d+)/password/$', - self.admin_site.admin_view(self.user_change_password)) - ) + super(UserAdmin, self).get_urls() - - def lookup_allowed(self, lookup, value): - # See #20078: we don't want to allow any lookups involving passwords. - if lookup.startswith('password'): - return False - return super(UserAdmin, self).lookup_allowed(lookup, value) - - @sensitive_post_parameters_m - @csrf_protect_m - @transaction.atomic - def add_view(self, request, form_url='', extra_context=None): - # It's an error for a user to have add permission but NOT change - # permission for users. If we allowed such users to add users, they - # could create superusers, which would mean they would essentially have - # the permission to change users. To avoid the problem entirely, we - # disallow users from adding users if they don't have change - # permission. - if not self.has_change_permission(request): - if self.has_add_permission(request) and settings.DEBUG: - # Raise Http404 in debug mode so that the user gets a helpful - # error message. - raise Http404( - 'Your user does not have the "Change user" permission. In ' - 'order to add users, Django requires that your user ' - 'account have both the "Add user" and "Change user" ' - 'permissions set.') - raise PermissionDenied - if extra_context is None: - extra_context = {} - username_field = self.model._meta.get_field(self.model.USERNAME_FIELD) - defaults = { - 'auto_populated_fields': (), - 'username_help_text': username_field.help_text, - } - extra_context.update(defaults) - return super(UserAdmin, self).add_view(request, form_url, - extra_context) - - @sensitive_post_parameters_m - def user_change_password(self, request, id, form_url=''): - if not self.has_change_permission(request): - raise PermissionDenied - user = get_object_or_404(self.get_queryset(request), pk=id) - if request.method == 'POST': - form = self.change_password_form(user, request.POST) - if form.is_valid(): - form.save() - change_message = self.construct_change_message(request, form, None) - self.log_change(request, user, change_message) - msg = ugettext('Password changed successfully.') - messages.success(request, msg) - update_session_auth_hash(request, form.user) - return HttpResponseRedirect('..') - else: - form = self.change_password_form(user) - - fieldsets = [(None, {'fields': list(form.base_fields)})] - adminForm = admin.helpers.AdminForm(form, fieldsets, {}) - - context = { - 'title': _('Change password: %s') % escape(user.get_username()), - 'adminForm': adminForm, - 'form_url': form_url, - 'form': form, - 'is_popup': (IS_POPUP_VAR in request.POST or - IS_POPUP_VAR in request.GET), - 'add': True, - 'change': False, - 'has_delete_permission': False, - 'has_change_permission': True, - 'has_absolute_url': False, - 'opts': self.model._meta, - 'original': user, - 'save_as': False, - 'show_save': True, - } - context.update(admin.site.each_context()) - return TemplateResponse(request, - self.change_user_password_template or - 'admin/auth/user/change_password.html', - context, current_app=self.admin_site.name) - - def response_add(self, request, obj, post_url_continue=None): - """ - Determines the HttpResponse for the add_view stage. It mostly defers to - its superclass implementation but is customized because the User model - has a slightly different workflow. - """ - # We should allow further modification of the user just added i.e. the - # 'Save' button should behave like the 'Save and continue editing' - # button except in two scenarios: - # * The user has pressed the 'Save and add another' button - # * We are adding a user in a popup - if '_addanother' not in request.POST and IS_POPUP_VAR not in request.POST: - request.POST['_continue'] = 1 - return super(UserAdmin, self).response_add(request, obj, - post_url_continue) - - # ------------------------------------------------------------------------ - # end stuff copied from ModelAdmin.UserAdmin - # ------------------------------------------------------------------------ - - class DashboardViewAdmin(PlanetStackBaseAdmin): fieldsets = [('Dashboard View Details', {'fields': ['backend_status_text', 'name', 'url'], -- 2.43.0