Commit 274bd67c authored by Luke Plant's avatar Luke Plant
Browse files

[1.1.X] Fixed #15103 - SuspiciousOperation with limit_choices_to and raw_id_fields

Thanks to natrius for the report.

This patch also fixes some unicode bugs in affected code.

Backport of [15347] from trunk. Backported to 1.1.X because this was
a regression caused by a security fix backported to 1.1.X.

git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.1.X@15350 bcc190cf-cafb-0310-a4f2-bffc1f526a37
parent 703dc822
Loading
Loading
Loading
Loading
+10 −1
Original line number Diff line number Diff line
@@ -173,7 +173,16 @@ class BaseModelAdmin(object):
        return None
    declared_fieldsets = property(_declared_fieldsets)

    def lookup_allowed(self, lookup):
    def lookup_allowed(self, lookup, value):
        model = self.model
        # Check FKey lookups that are allowed, so that popups produced by
        # ForeignKeyRawIdWidget, on the basis of ForeignKey.limit_choices_to,
        # are allowed to work.
        for l in model._meta.related_fkey_lookups:
            for k, v in widgets.url_params_from_lookup_dict(l).items():
                if k == lookup and v == value:
                    return True

        parts = lookup.split(LOOKUP_SEP)

        # Last term in lookup is a query term (__exact, __startswith etc)
+6 −4
Original line number Diff line number Diff line
@@ -184,16 +184,18 @@ class ChangeList(object):

            # if key ends with __in, split parameter into separate values
            if key.endswith('__in'):
                lookup_params[key] = value.split(',')
                value = value.split(',')
                lookup_params[key] = value

            # if key ends with __isnull, special case '' and false
            if key.endswith('__isnull'):
                if value.lower() in ('', 'false'):
                    lookup_params[key] = False
                    value = False
                else:
                    lookup_params[key] = True
                    value = True
                lookup_params[key] = value

            if not self.model_admin.lookup_allowed(key):
            if not self.model_admin.lookup_allowed(key, value):
                raise SuspiciousOperation(
                    "Filtering by %s not allowed" % key
                )
+22 −15
Original line number Diff line number Diff line
@@ -97,6 +97,23 @@ class AdminFileWidget(forms.FileInput):
        output.append(super(AdminFileWidget, self).render(name, value, attrs))
        return mark_safe(u''.join(output))

def url_params_from_lookup_dict(lookups):
    """
    Converts the type of lookups specified in a ForeignKey limit_choices_to
    attribute to a dictionary of query parameters
    """
    params = {}
    if lookups and hasattr(lookups, 'items'):
        items = []
        for k, v in lookups.items():
            if isinstance(v, list):
                v = u','.join([str(x) for x in v])
            else:
                v = unicode(v)
            items.append((k, v))
        params.update(dict(items))
    return params

class ForeignKeyRawIdWidget(forms.TextInput):
    """
    A Widget for displaying ForeignKeys in the "raw_id" interface rather than
@@ -112,33 +129,23 @@ class ForeignKeyRawIdWidget(forms.TextInput):
        related_url = '../../../%s/%s/' % (self.rel.to._meta.app_label, self.rel.to._meta.object_name.lower())
        params = self.url_parameters()
        if params:
            url = '?' + '&'.join(['%s=%s' % (k, v) for k, v in params.items()])
            url = u'?' + u'&'.join([u'%s=%s' % (k, v) for k, v in params.items()])
        else:
            url = ''
            url = u''
        if not attrs.has_key('class'):
            attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript looks for this hook.
        output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)]
        # TODO: "id_" is hard-coded here. This should instead use the correct
        # API to determine the ID dynamically.
        output.append('<a href="%s%s" class="related-lookup" id="lookup_id_%s" onclick="return showRelatedObjectLookupPopup(this);"> ' % \
        output.append(u'<a href="%s%s" class="related-lookup" id="lookup_id_%s" onclick="return showRelatedObjectLookupPopup(this);"> ' % \
            (related_url, url, name))
        output.append('<img src="%simg/admin/selector-search.gif" width="16" height="16" alt="%s" /></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Lookup')))
        output.append(u'<img src="%simg/admin/selector-search.gif" width="16" height="16" alt="%s" /></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Lookup')))
        if value:
            output.append(self.label_for_value(value))
        return mark_safe(u''.join(output))

    def base_url_parameters(self):
        params = {}
        if self.rel.limit_choices_to and hasattr(self.rel.limit_choices_to, 'items'):
            items = []
            for k, v in self.rel.limit_choices_to.items():
                if isinstance(v, list):
                    v = ','.join([str(x) for x in v])
                else:
                    v = str(v)
                items.append((k, v))
            params.update(dict(items))
        return params
        return url_params_from_lookup_dict(self.rel.limit_choices_to)

    def url_parameters(self):
        from django.contrib.admin.views.main import TO_FIELD_VAR
+2 −0
Original line number Diff line number Diff line
@@ -745,6 +745,8 @@ class ForeignKey(RelatedField, Field):

    def contribute_to_related_class(self, cls, related):
        setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related))
        if self.rel.limit_choices_to:
            cls._meta.related_fkey_lookups.append(self.rel.limit_choices_to)

    def formfield(self, **kwargs):
        defaults = {
+4 −0
Original line number Diff line number Diff line
@@ -53,6 +53,10 @@ class Options(object):
        self.abstract_managers = []
        self.concrete_managers = []

        # List of all lookups defined in ForeignKey 'limit_choices_to' options
        # from *other* models. Needed for some admin checks. Internal use only.
        self.related_fkey_lookups = []

    def contribute_to_class(self, cls, name):
        from django.db import connection
        from django.db.backends.util import truncate_name
Loading