#33106: ModelMultipleChoiceField clean() method calls prepare_value instead of
to_python
--------------------------------------+------------------------
               Reporter:  Adam McKay  |          Owner:  nobody
                   Type:  Bug         |         Status:  new
              Component:  Forms       |        Version:  3.2
               Severity:  Normal      |       Keywords:
           Triage Stage:  Unreviewed  |      Has patch:  0
    Needs documentation:  0           |    Needs tests:  0
Patch needs improvement:  0           |  Easy pickings:  0
                  UI/UX:  0           |
--------------------------------------+------------------------
 The [https://docs.djangoproject.com/en/3.2/ref/forms/validation/#form-and-
 field-validation Form and field validation documentation] says:

     The clean() method on a Field subclass is responsible for running
 to_python(), validate(), and run_validators() in the correct order and
 propagating their errors.

 However the `clean()` method of `ModelMultipleChoiceField` calls `value =
 self.prepare_value(value)` which is causing issues for my use case of
 hiding primary keys on forms using a [https://hashids.org/ hashids] module
 as I am subclassing the `to_python` and `prepare_values`  methods to
 ensure the `pk` exposed to users are encoded/decoded as appropriate,
 however the `to_python` method is not called as an error `“encodedValue”
 is not a valid value.` is returned to the user because the value is not
 correctly decoded when it is checked as a valid choice.

 I have written a test case for
 `tests/model_forms/test_modelchoicefield.py` in `ModelChoiceFieldTests`
 which for this test merely adds `42` to the `pk` before exposing it to the
 user to demonstrate the behaviour:

 {{{
     def test_clean_serializes_input(self):
         class
 EncryptedModelMultipleChoiceField(forms.ModelMultipleChoiceField):
             """Hide pk by modifying by 42"""
             def to_python(self, value):
                 if not value:
                     return []
                 if hasattr(value, '__iter__'):
                     return [int(getattr(v, 'pk', v)) - 42 for v in value]
                 return int(getattr(value, 'pk', value)) - 42

             def prepare_value(self, value):
                 if not value:
                     return []
                 if hasattr(value, '__iter__'):
                     return [int(getattr(v, 'pk', v)) + 42 for v in value]
                 return int(getattr(value, 'pk', value)) + 42

         f = EncryptedModelMultipleChoiceField(Category.objects.all())
         print(f.widget.render('name', []),)
         self.assertHTMLEqual(
             f.widget.render('name', []),
             """<select name="name" multiple>
                 <option value="%s">Entertainment</option>
                 <option value="%s">A test</option>
                 <option value="%s">Third</option>
             </select>""" % (self.c1.pk + 42, self.c2.pk + 42, self.c3.pk +
 42),
         )
         with self.assertRaises(ValidationError):
             f.clean('')
         with self.assertRaises(ValidationError):
             f.clean(None)
         with self.assertRaises(ValidationError):
             f.clean(0)

         self.assertEqual(['Entertainment'], [c.name for c in
 f.clean([self.c1.pk + 42])])
         self.assertEqual(['Entertainment', 'Third'], [c.name for c in
 f.clean([self.c1.pk + 42, self.c3.pk + 42])])
 }}}

-- 
Ticket URL: <https://code.djangoproject.com/ticket/33106>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/051.af739002c82c97238ba84dab8e870931%40djangoproject.com.

Reply via email to