+63
-5
lines changedFilter options
+63
-5
lines changed Original file line number Diff line number Diff line change
@@ -1221,8 +1221,7 @@ def __init__(self, queryset, cache_choices=False, required=True,
1221
1221
def to_python(self, value):
1222
1222
if not value:
1223
1223
return []
1224
-
to_py = super(ModelMultipleChoiceField, self).to_python
1225
-
return [to_py(val) for val in value]
1224
+
return list(self._check_values(value))
1226
1225
1227
1226
def clean(self, value):
1228
1227
if self.required and not value:
@@ -1231,7 +1230,29 @@ def clean(self, value):
1231
1230
return self.queryset.none()
1232
1231
if not isinstance(value, (list, tuple)):
1233
1232
raise ValidationError(self.error_messages['list'], code='list')
1233
+
qs = self._check_values(value)
1234
+
# Since this overrides the inherited ModelChoiceField.clean
1235
+
# we run custom validators here
1236
+
self.run_validators(value)
1237
+
return qs
1238
+
1239
+
def _check_values(self, value):
1240
+
"""
1241
+
Given a list of possible PK values, returns a QuerySet of the
1242
+
corresponding objects. Raises a ValidationError if a given value is
1243
+
invalid (not a valid PK, not in the queryset, etc.)
1244
+
"""
1234
1245
key = self.to_field_name or 'pk'
1246
+
# deduplicate given values to avoid creating many querysets or
1247
+
# requiring the database backend deduplicate efficiently.
1248
+
try:
1249
+
value = frozenset(value)
1250
+
except TypeError:
1251
+
# list of lists isn't hashable, for example
1252
+
raise ValidationError(
1253
+
self.error_messages['list'],
1254
+
code='list',
1255
+
)
1235
1256
for pk in value:
1236
1257
try:
1237
1258
self.queryset.filter(**{key: pk})
@@ -1250,9 +1271,6 @@ def clean(self, value):
1250
1271
code='invalid_choice',
1251
1272
params={'value': val},
1252
1273
)
1253
-
# Since this overrides the inherited ModelChoiceField.clean
1254
-
# we run custom validators here
1255
-
self.run_validators(value)
1256
1274
return qs
1257
1275
1258
1276
def prepare_value(self, value):
Original file line number Diff line number Diff line change
@@ -58,3 +58,12 @@ Note, however, that this view has always carried a warning that it is not
58
58
hardened for production use and should be used only as a development aid. Now
59
59
may be a good time to audit your project and serve your files in production
60
60
using a real front-end web server if you are not doing so.
61
+
62
+
Database denial-of-service with ``ModelMultipleChoiceField``
63
+
============================================================
64
+
65
+
Given a form that uses ``ModelMultipleChoiceField`` and
66
+
``show_hidden_initial=True`` (not a documented API), it was possible for a user
67
+
to cause an unreasonable number of SQL queries by submitting duplicate values
68
+
for the field's data. The validation logic in ``ModelMultipleChoiceField`` now
69
+
deduplicates submitted values to address this issue.
Original file line number Diff line number Diff line change
@@ -59,6 +59,15 @@ hardened for production use and should be used only as a development aid. Now
59
59
may be a good time to audit your project and serve your files in production
60
60
using a real front-end web server if you are not doing so.
61
61
62
+
Database denial-of-service with ``ModelMultipleChoiceField``
63
+
============================================================
64
+
65
+
Given a form that uses ``ModelMultipleChoiceField`` and
66
+
``show_hidden_initial=True`` (not a documented API), it was possible for a user
67
+
to cause an unreasonable number of SQL queries by submitting duplicate values
68
+
for the field's data. The validation logic in ``ModelMultipleChoiceField`` now
69
+
deduplicates submitted values to address this issue.
70
+
62
71
Bugfixes
63
72
========
64
73
Original file line number Diff line number Diff line change
@@ -134,6 +134,7 @@ dbshell
134
134
de
135
135
deconstruct
136
136
deconstructing
137
+
deduplicates
137
138
deepcopy
138
139
deserialization
139
140
deserialize
Original file line number Diff line number Diff line change
@@ -1573,6 +1573,27 @@ class WriterForm(forms.Form):
1573
1573
self.assertTrue(form.is_valid())
1574
1574
self.assertTrue(form.has_changed())
1575
1575
1576
+
def test_show_hidden_initial_changed_queries_efficiently(self):
1577
+
class WriterForm(forms.Form):
1578
+
persons = forms.ModelMultipleChoiceField(
1579
+
show_hidden_initial=True, queryset=Writer.objects.all())
1580
+
1581
+
writers = (Writer.objects.create(name=str(x)) for x in range(0, 50))
1582
+
writer_pks = tuple(x.pk for x in writers)
1583
+
form = WriterForm(data={'initial-persons': writer_pks})
1584
+
with self.assertNumQueries(1):
1585
+
self.assertTrue(form.has_changed())
1586
+
1587
+
def test_clean_does_deduplicate_values(self):
1588
+
class WriterForm(forms.Form):
1589
+
persons = forms.ModelMultipleChoiceField(queryset=Writer.objects.all())
1590
+
1591
+
person1 = Writer.objects.create(name="Person 1")
1592
+
form = WriterForm(data={})
1593
+
queryset = form.fields['persons'].clean([str(person1.pk)] * 50)
1594
+
sql, params = queryset.query.sql_with_params()
1595
+
self.assertEqual(len(params), 1)
1596
+
1576
1597
1577
1598
class ModelOneToOneFieldTests(TestCase):
1578
1599
def test_modelform_onetoonefield(self):
You can’t perform that action at this time.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4