mirror of
https://github.com/django/django.git
synced 2025-07-04 17:59:13 +00:00
[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
This commit is contained in:
parent
703dc82256
commit
274bd67c13
@ -173,7 +173,16 @@ class BaseModelAdmin(object):
|
|||||||
return None
|
return None
|
||||||
declared_fieldsets = property(_declared_fieldsets)
|
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)
|
parts = lookup.split(LOOKUP_SEP)
|
||||||
|
|
||||||
# Last term in lookup is a query term (__exact, __startswith etc)
|
# Last term in lookup is a query term (__exact, __startswith etc)
|
||||||
|
@ -184,16 +184,18 @@ class ChangeList(object):
|
|||||||
|
|
||||||
# if key ends with __in, split parameter into separate values
|
# if key ends with __in, split parameter into separate values
|
||||||
if key.endswith('__in'):
|
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 ends with __isnull, special case '' and false
|
||||||
if key.endswith('__isnull'):
|
if key.endswith('__isnull'):
|
||||||
if value.lower() in ('', 'false'):
|
if value.lower() in ('', 'false'):
|
||||||
lookup_params[key] = False
|
value = False
|
||||||
else:
|
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(
|
raise SuspiciousOperation(
|
||||||
"Filtering by %s not allowed" % key
|
"Filtering by %s not allowed" % key
|
||||||
)
|
)
|
||||||
|
@ -97,6 +97,23 @@ class AdminFileWidget(forms.FileInput):
|
|||||||
output.append(super(AdminFileWidget, self).render(name, value, attrs))
|
output.append(super(AdminFileWidget, self).render(name, value, attrs))
|
||||||
return mark_safe(u''.join(output))
|
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):
|
class ForeignKeyRawIdWidget(forms.TextInput):
|
||||||
"""
|
"""
|
||||||
A Widget for displaying ForeignKeys in the "raw_id" interface rather than
|
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())
|
related_url = '../../../%s/%s/' % (self.rel.to._meta.app_label, self.rel.to._meta.object_name.lower())
|
||||||
params = self.url_parameters()
|
params = self.url_parameters()
|
||||||
if params:
|
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:
|
else:
|
||||||
url = ''
|
url = u''
|
||||||
if not attrs.has_key('class'):
|
if not attrs.has_key('class'):
|
||||||
attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript looks for this hook.
|
attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript looks for this hook.
|
||||||
output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)]
|
output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)]
|
||||||
# TODO: "id_" is hard-coded here. This should instead use the correct
|
# TODO: "id_" is hard-coded here. This should instead use the correct
|
||||||
# API to determine the ID dynamically.
|
# 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))
|
(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:
|
if value:
|
||||||
output.append(self.label_for_value(value))
|
output.append(self.label_for_value(value))
|
||||||
return mark_safe(u''.join(output))
|
return mark_safe(u''.join(output))
|
||||||
|
|
||||||
def base_url_parameters(self):
|
def base_url_parameters(self):
|
||||||
params = {}
|
return url_params_from_lookup_dict(self.rel.limit_choices_to)
|
||||||
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
|
|
||||||
|
|
||||||
def url_parameters(self):
|
def url_parameters(self):
|
||||||
from django.contrib.admin.views.main import TO_FIELD_VAR
|
from django.contrib.admin.views.main import TO_FIELD_VAR
|
||||||
|
@ -745,6 +745,8 @@ class ForeignKey(RelatedField, Field):
|
|||||||
|
|
||||||
def contribute_to_related_class(self, cls, related):
|
def contribute_to_related_class(self, cls, related):
|
||||||
setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(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):
|
def formfield(self, **kwargs):
|
||||||
defaults = {
|
defaults = {
|
||||||
|
@ -53,6 +53,10 @@ class Options(object):
|
|||||||
self.abstract_managers = []
|
self.abstract_managers = []
|
||||||
self.concrete_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):
|
def contribute_to_class(self, cls, name):
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.db.backends.util import truncate_name
|
from django.db.backends.util import truncate_name
|
||||||
|
@ -146,6 +146,26 @@ class Thing(models.Model):
|
|||||||
class ThingAdmin(admin.ModelAdmin):
|
class ThingAdmin(admin.ModelAdmin):
|
||||||
list_filter = ('color',)
|
list_filter = ('color',)
|
||||||
|
|
||||||
|
class Actor(models.Model):
|
||||||
|
name = models.CharField(max_length=50)
|
||||||
|
age = models.IntegerField()
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Inquisition(models.Model):
|
||||||
|
expected = models.BooleanField()
|
||||||
|
leader = models.ForeignKey(Actor)
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.expected
|
||||||
|
|
||||||
|
class Sketch(models.Model):
|
||||||
|
title = models.CharField(max_length=100)
|
||||||
|
inquisition = models.ForeignKey(Inquisition, limit_choices_to={'leader__name': 'Palin',
|
||||||
|
'leader__age': 27,
|
||||||
|
})
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
class Fabric(models.Model):
|
class Fabric(models.Model):
|
||||||
NG_CHOICES = (
|
NG_CHOICES = (
|
||||||
('Textured', (
|
('Textured', (
|
||||||
@ -519,6 +539,9 @@ admin.site.register(Section, save_as=True, inlines=[ArticleInline])
|
|||||||
admin.site.register(ModelWithStringPrimaryKey)
|
admin.site.register(ModelWithStringPrimaryKey)
|
||||||
admin.site.register(Color)
|
admin.site.register(Color)
|
||||||
admin.site.register(Thing, ThingAdmin)
|
admin.site.register(Thing, ThingAdmin)
|
||||||
|
admin.site.register(Actor)
|
||||||
|
admin.site.register(Inquisition)
|
||||||
|
admin.site.register(Sketch)
|
||||||
admin.site.register(Person, PersonAdmin)
|
admin.site.register(Person, PersonAdmin)
|
||||||
admin.site.register(Persona, PersonaAdmin)
|
admin.site.register(Persona, PersonaAdmin)
|
||||||
admin.site.register(Subscriber, SubscriberAdmin)
|
admin.site.register(Subscriber, SubscriberAdmin)
|
||||||
|
@ -300,6 +300,17 @@ class AdminViewBasicTest(TestCase):
|
|||||||
except SuspiciousOperation:
|
except SuspiciousOperation:
|
||||||
self.fail("Filters should be allowed if they involve a local field without the need to whitelist them in list_filter or date_hierarchy.")
|
self.fail("Filters should be allowed if they involve a local field without the need to whitelist them in list_filter or date_hierarchy.")
|
||||||
|
|
||||||
|
def test_allowed_filtering_15103(self):
|
||||||
|
"""
|
||||||
|
Regressions test for ticket 15103 - filtering on fields defined in a
|
||||||
|
ForeignKey 'limit_choices_to' should be allowed, otherwise raw_id_fields
|
||||||
|
can break.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.client.get("/test_admin/admin/admin_views/inquisition/?leader__name=Palin&leader__age=27")
|
||||||
|
except SuspiciousOperation:
|
||||||
|
self.fail("Filters should be allowed if they are defined on a ForeignKey pointing to this model")
|
||||||
|
|
||||||
class SaveAsTests(TestCase):
|
class SaveAsTests(TestCase):
|
||||||
fixtures = ['admin-views-users.xml','admin-views-person.xml']
|
fixtures = ['admin-views-users.xml','admin-views-person.xml']
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user