Skip to content


Subversion checkout URL

You can clone with
Download ZIP
Browse files

CMS_PERMISSION: optionally use raw id user lookups

The "view restrictions" and "page permissions" inlines on the "page"
admin change forms can cause performance problems where there are many
thousands of users being put into simple select boxes. Added a new
setting (CMS_RAW_ID_USERS) that, if set, forces the inlines on that page
to use standard Django admin "raw ID" lookups rather than select boxes.
The restriction on which users may be selected should still be honoured
by the new widget because we set the 'limit_choices_to' attribute to be
the same as the queryset on the original widget.

Using raw_id_fields in combination with limit_choices_to blows up if you
have many thousands of users. For this reason, we only apply this limit
if the number of users is relatively small (< 500, though that figure is
somewhat arbitrary). If the number of users we need to limit to is
greater than that, we use the usual input field instead unless the user
is a CMS superuser, in which case we bypass the limit. Unfortunately,
this means that non-superusers won't see any benefit from this change.
  • Loading branch information...
commit 4afe5e15f45c7aa3df17cdeef2cf84b5c29eed6a 1 parent 686ed2f
@jimr jimr authored
2  cms/
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-__version__ = '2.2'
+__version__ = '2.2.1'
# patch settings
41 cms/admin/
@@ -2,12 +2,12 @@
from cms.apphook_pool import apphook_pool
from cms.forms.widgets import UserSelectAdminWidget
from cms.models import (Page, PagePermission, PageUser, ACCESS_PAGE,
- PageUserGroup)
+ PageUserGroup, GlobalPagePermission)
from cms.utils.mail import mail_page_user_change
from import is_valid_page_slug
from cms.utils.page_resolver import get_page_from_path
from cms.utils.permissions import (get_current_user, get_subordinate_users,
- get_subordinate_groups)
+ get_subordinate_groups, get_user_permission_level)
from cms.utils.urlutils import any_path_re
from django import forms
from django.conf import settings
@@ -188,14 +188,45 @@ class PagePermissionInlineAdminForm(forms.ModelForm):
level or under him in choosen page tree, and users which were created by him,
but aren't assigned to higher page level than current user.
- user = forms.ModelChoiceField('user', label=_('user'), widget=UserSelectAdminWidget, required=False)
page = forms.ModelChoiceField(Page, label=_('user'), widget=HiddenInput(), required=True)
def __init__(self, *args, **kwargs):
super(PagePermissionInlineAdminForm, self).__init__(*args, **kwargs)
user = get_current_user() # current user from threadlocals
- self.fields['user'].queryset = get_subordinate_users(user)
- self.fields['user'].widget.user = user # assign current user
+ sub_users = get_subordinate_users(user)
+ limit_choices = True
+ use_raw_id = False
+ if hasattr(settings, 'CMS_RAW_ID_USERS') and settings.CMS_RAW_ID_USERS:
+ if len(sub_users) < 500:
+ # If there aren't too many users, proceed as normal and use a raw
+ # id field with limit_choices_to
+ limit_choices = True
+ use_raw_id = True
+ elif get_user_permission_level(user) == 0:
+ # If there are enough choices to possibly cause a 414 request
+ # URI too large error, we only proceed with the raw id field if
+ # the user is a superuser & thus can legitimately circumvent
+ # the limit_choices_to condition.
+ limit_choices = False
+ use_raw_id = True
+ # We can't use the fancy custom widget if the admin form wants to use a
+ # raw id field for the user
+ if use_raw_id:
+ # We can't set a queryset on a raw id lookup, but we can use the
+ # fact that it respects the limit_choices_to parameter.
+ from django.contrib.admin.widgets import ForeignKeyRawIdWidget
+ if isinstance(self.fields['user'].widget, ForeignKeyRawIdWidget):
+ if limit_choices:
+ self.fields['user'].widget.rel.limit_choices_to = dict(
+ id__in=list(sub_users.values_list('pk', flat=True))
+ )
+ else:
+ self.fields['user'].widget = UserSelectAdminWidget()
+ self.fields['user'].queryset = sub_users
+ self.fields['user'].widget.user = user # assign current user
self.fields['group'].queryset = get_subordinate_groups(user)
def clean(self):
9 cms/admin/
@@ -22,6 +22,15 @@ class PagePermissionInlineAdmin(admin.TabularInline):
classes = ['collapse', 'collapsed']
exclude = ['can_view']
+ def __getattribute__(self, name):
+ # Dynamically set raw_id_fields based on settings
+ if name == 'raw_id_fields':
+ if hasattr(settings, 'CMS_RAW_ID_USERS') and settings.CMS_RAW_ID_USERS:
+ return ['user']
+ return []
+ else:
+ return super(PagePermissionInlineAdmin, self).__getattribute__(name)
def queryset(self, request):
Queryset change, so user with global change permissions can see
Please sign in to comment.
Something went wrong with that request. Please try again.