Plain is headed towards 1.0! Subscribe for development updates โ†’

  1"""
  2Helper functions for creating Form classes from Plain models
  3and database field objects.
  4"""
  5
  6from itertools import chain
  7
  8from plain.exceptions import (
  9    NON_FIELD_ERRORS,
 10    FieldError,
 11    ImproperlyConfigured,
 12    ValidationError,
 13)
 14from plain.forms import fields
 15from plain.forms.fields import ChoiceField, Field
 16from plain.forms.forms import BaseForm, DeclarativeFieldsMetaclass
 17
 18__all__ = (
 19    "ModelForm",
 20    "BaseModelForm",
 21    "model_to_dict",
 22    "fields_for_model",
 23    "ModelChoiceField",
 24    "ModelMultipleChoiceField",
 25)
 26
 27
 28def construct_instance(form, instance, fields=None):
 29    """
 30    Construct and return a model instance from the bound ``form``'s
 31    ``cleaned_data``, but do not save the returned instance to the database.
 32    """
 33    from plain import models
 34
 35    opts = instance._meta
 36
 37    cleaned_data = form.cleaned_data
 38    file_field_list = []
 39    for f in opts.fields:
 40        if isinstance(f, models.AutoField) or f.name not in cleaned_data:
 41            continue
 42        if fields is not None and f.name not in fields:
 43            continue
 44        # Leave defaults for fields that aren't in POST data, except for
 45        # checkbox inputs because they don't appear in POST data if not checked.
 46        if (
 47            f.has_default()
 48            and form.add_prefix(f.name) not in form.data
 49            and form.add_prefix(f.name) not in form.files
 50            # and form[f.name].field.widget.value_omitted_from_data(
 51            #     form.data, form.files, form.add_prefix(f.name)
 52            # )
 53            and cleaned_data.get(f.name) in form[f.name].field.empty_values
 54        ):
 55            continue
 56
 57        f.save_form_data(instance, cleaned_data[f.name])
 58
 59    for f in file_field_list:
 60        f.save_form_data(instance, cleaned_data[f.name])
 61
 62    return instance
 63
 64
 65# ModelForms #################################################################
 66
 67
 68def model_to_dict(instance, fields=None):
 69    """
 70    Return a dict containing the data in ``instance`` suitable for passing as
 71    a Form's ``initial`` keyword argument.
 72
 73    ``fields`` is an optional list of field names. If provided, return only the
 74    named.
 75    """
 76    opts = instance._meta
 77    data = {}
 78    for f in chain(opts.concrete_fields, opts.many_to_many):
 79        if fields is not None and f.name not in fields:
 80            continue
 81        data[f.name] = f.value_from_object(instance)
 82    return data
 83
 84
 85def fields_for_model(
 86    model,
 87    fields=None,
 88    formfield_callback=None,
 89    error_messages=None,
 90    field_classes=None,
 91):
 92    """
 93    Return a dictionary containing form fields for the given model.
 94
 95    ``fields`` is an optional list of field names. If provided, return only the
 96    named fields.
 97
 98    ``formfield_callback`` is a callable that takes a model field and returns
 99    a form field.
100
101    ``error_messages`` is a dictionary of model field names mapped to a
102    dictionary of error messages.
103
104    ``field_classes`` is a dictionary of model field names mapped to a form
105    field class.
106    """
107    field_dict = {}
108    ignored = []
109    opts = model._meta
110
111    for f in sorted(chain(opts.concrete_fields, opts.many_to_many)):
112        if fields is not None and f.name not in fields:
113            continue
114
115        kwargs = {}
116        if error_messages and f.name in error_messages:
117            kwargs["error_messages"] = error_messages[f.name]
118        if field_classes and f.name in field_classes:
119            kwargs["form_class"] = field_classes[f.name]
120
121        if formfield_callback is None:
122            formfield = modelfield_to_formfield(f, **kwargs)
123        elif not callable(formfield_callback):
124            raise TypeError("formfield_callback must be a function or callable")
125        else:
126            formfield = formfield_callback(f, **kwargs)
127
128        if formfield:
129            field_dict[f.name] = formfield
130        else:
131            ignored.append(f.name)
132    if fields:
133        field_dict = {f: field_dict.get(f) for f in fields if f not in ignored}
134    return field_dict
135
136
137class ModelFormOptions:
138    def __init__(self, options=None):
139        self.model = getattr(options, "model", None)
140        self.fields = getattr(options, "fields", None)
141        self.error_messages = getattr(options, "error_messages", None)
142        self.field_classes = getattr(options, "field_classes", None)
143        self.formfield_callback = getattr(options, "formfield_callback", None)
144
145
146class ModelFormMetaclass(DeclarativeFieldsMetaclass):
147    def __new__(mcs, name, bases, attrs):
148        new_class = super().__new__(mcs, name, bases, attrs)
149
150        if bases == (BaseModelForm,):
151            return new_class
152
153        opts = new_class._meta = ModelFormOptions(getattr(new_class, "Meta", None))
154
155        # We check if a string was passed to `fields`,
156        # which is likely to be a mistake where the user typed ('foo') instead
157        # of ('foo',)
158        for opt in ["fields"]:
159            value = getattr(opts, opt)
160            if isinstance(value, str):
161                msg = (
162                    f"{new_class.__name__}.Meta.{opt} cannot be a string. "
163                    f"Did you mean to type: ('{value}',)?"
164                )
165                raise TypeError(msg)
166
167        if opts.model:
168            # If a model is defined, extract form fields from it.
169            if opts.fields is None:
170                raise ImproperlyConfigured(
171                    "Creating a ModelForm without the 'fields' attribute "
172                    f"is prohibited; form {name} "
173                    "needs updating."
174                )
175
176            fields = fields_for_model(
177                opts.model,
178                opts.fields,
179                opts.formfield_callback,
180                opts.error_messages,
181                opts.field_classes,
182            )
183
184            # make sure opts.fields doesn't specify an invalid field
185            none_model_fields = {k for k, v in fields.items() if not v}
186            missing_fields = none_model_fields.difference(new_class.declared_fields)
187            if missing_fields:
188                message = "Unknown field(s) (%s) specified for %s"
189                message %= (", ".join(missing_fields), opts.model.__name__)
190                raise FieldError(message)
191            # Override default model fields with any custom declared ones
192            # (plus, include all the other declared fields).
193            fields.update(new_class.declared_fields)
194        else:
195            fields = new_class.declared_fields
196
197        new_class.base_fields = fields
198
199        return new_class
200
201
202class BaseModelForm(BaseForm):
203    def __init__(
204        self,
205        *,
206        request,
207        auto_id="id_%s",
208        prefix=None,
209        initial=None,
210        instance=None,
211    ):
212        opts = self._meta
213        if opts.model is None:
214            raise ValueError("ModelForm has no model class specified.")
215        if instance is None:
216            # if we didn't get an instance, instantiate a new one
217            self.instance = opts.model()
218            object_data = {}
219        else:
220            self.instance = instance
221            object_data = model_to_dict(instance, opts.fields)
222        # if initial was provided, it should override the values from instance
223        if initial is not None:
224            object_data.update(initial)
225        # self._validate_unique will be set to True by BaseModelForm.clean().
226        # It is False by default so overriding self.clean() and failing to call
227        # super will stop validate_unique from being called.
228        self._validate_unique = False
229        super().__init__(
230            request=request,
231            auto_id=auto_id,
232            prefix=prefix,
233            initial=object_data,
234        )
235
236    def _get_validation_exclusions(self):
237        """
238        For backwards-compatibility, exclude several types of fields from model
239        validation. See tickets #12507, #12521, #12553.
240        """
241        exclude = set()
242        # Build up a list of fields that should be excluded from model field
243        # validation and unique checks.
244        for f in self.instance._meta.fields:
245            field = f.name
246            # Exclude fields that aren't on the form. The developer may be
247            # adding these values to the model after form validation.
248            if field not in self.fields:
249                exclude.add(f.name)
250
251            # Don't perform model validation on fields that were defined
252            # manually on the form and excluded via the ModelForm's Meta
253            # class. See #12901.
254            elif self._meta.fields and field not in self._meta.fields:
255                exclude.add(f.name)
256
257            # Exclude fields that failed form validation. There's no need for
258            # the model fields to validate them as well.
259            elif field in self._errors:
260                exclude.add(f.name)
261
262            # Exclude empty fields that are not required by the form, if the
263            # underlying model field is required. This keeps the model field
264            # from raising a required error. Note: don't exclude the field from
265            # validation if the model field allows blanks. If it does, the blank
266            # value may be included in a unique check, so cannot be excluded
267            # from validation.
268            else:
269                form_field = self.fields[field]
270                field_value = self.cleaned_data.get(field)
271                if (
272                    f.required
273                    and not form_field.required
274                    and field_value in form_field.empty_values
275                ):
276                    exclude.add(f.name)
277        return exclude
278
279    def clean(self):
280        self._validate_unique = True
281        return self.cleaned_data
282
283    def _update_errors(self, errors):
284        # Override any validation error messages defined at the model level
285        # with those defined at the form level.
286        opts = self._meta
287
288        # Allow the model generated by construct_instance() to raise
289        # ValidationError and have them handled in the same way as others.
290        if hasattr(errors, "error_dict"):
291            error_dict = errors.error_dict
292        else:
293            error_dict = {NON_FIELD_ERRORS: errors}
294
295        for field, messages in error_dict.items():
296            if (
297                field == NON_FIELD_ERRORS
298                and opts.error_messages
299                and NON_FIELD_ERRORS in opts.error_messages
300            ):
301                error_messages = opts.error_messages[NON_FIELD_ERRORS]
302            elif field in self.fields:
303                error_messages = self.fields[field].error_messages
304            else:
305                continue
306
307            for message in messages:
308                if (
309                    isinstance(message, ValidationError)
310                    and message.code in error_messages
311                ):
312                    message.message = error_messages[message.code]
313
314        self.add_error(None, errors)
315
316    def _post_clean(self):
317        opts = self._meta
318
319        exclude = self._get_validation_exclusions()
320
321        # Foreign Keys being used to represent inline relationships
322        # are excluded from basic field value validation. This is for two
323        # reasons: firstly, the value may not be supplied (#12507; the
324        # case of providing new values to the admin); secondly the
325        # object being referred to may not yet fully exist (#12749).
326        # However, these fields *must* be included in uniqueness checks,
327        # so this can't be part of _get_validation_exclusions().
328        for name, field in self.fields.items():
329            if isinstance(field, InlineForeignKeyField):
330                exclude.add(name)
331
332        try:
333            self.instance = construct_instance(self, self.instance, opts.fields)
334        except ValidationError as e:
335            self._update_errors(e)
336
337        try:
338            self.instance.full_clean(exclude=exclude, validate_unique=False)
339        except ValidationError as e:
340            self._update_errors(e)
341
342        # Validate uniqueness if needed.
343        if self._validate_unique:
344            self.validate_unique()
345
346    def validate_unique(self):
347        """
348        Call the instance's validate_unique() method and update the form's
349        validation errors if any were raised.
350        """
351        exclude = self._get_validation_exclusions()
352        try:
353            self.instance.validate_unique(exclude=exclude)
354        except ValidationError as e:
355            self._update_errors(e)
356
357    def _save_m2m(self):
358        """
359        Save the many-to-many fields and generic relations for this form.
360        """
361        cleaned_data = self.cleaned_data
362        fields = self._meta.fields
363        opts = self.instance._meta
364
365        for f in opts.many_to_many:
366            if not hasattr(f, "save_form_data"):
367                continue
368            if fields and f.name not in fields:
369                continue
370            if f.name in cleaned_data:
371                f.save_form_data(self.instance, cleaned_data[f.name])
372
373    def save(self, commit=True):
374        """
375        Save this form's self.instance object if commit=True. Otherwise, add
376        a save_m2m() method to the form which can be called after the instance
377        is saved manually at a later time. Return the model instance.
378        """
379        if self.errors:
380            raise ValueError(
381                "The {} could not be {} because the data didn't validate.".format(
382                    self.instance._meta.object_name,
383                    "created" if self.instance._state.adding else "changed",
384                )
385            )
386        if commit:
387            # If committing, save the instance and the m2m data immediately.
388            self.instance.save(clean_and_validate=False)
389            self._save_m2m()
390        else:
391            # If not committing, add a method to the form to allow deferred
392            # saving of m2m data.
393            self.save_m2m = self._save_m2m
394        return self.instance
395
396
397class ModelForm(BaseModelForm, metaclass=ModelFormMetaclass):
398    pass
399
400
401# Fields #####################################################################
402
403
404class InlineForeignKeyField(Field):
405    """
406    A basic integer field that deals with validating the given value to a
407    given parent instance in an inline.
408    """
409
410    default_error_messages = {
411        "invalid_choice": "The inline value did not match the parent instance.",
412    }
413
414    def __init__(self, parent_instance, *args, pk_field=False, to_field=None, **kwargs):
415        self.parent_instance = parent_instance
416        self.pk_field = pk_field
417        self.to_field = to_field
418        if self.parent_instance is not None:
419            if self.to_field:
420                kwargs["initial"] = getattr(self.parent_instance, self.to_field)
421            else:
422                kwargs["initial"] = self.parent_instance.pk
423        kwargs["required"] = False
424        super().__init__(*args, **kwargs)
425
426    def clean(self, value):
427        if value in self.empty_values:
428            if self.pk_field:
429                return None
430            # if there is no value act as we did before.
431            return self.parent_instance
432        # ensure the we compare the values as equal types.
433        if self.to_field:
434            orig = getattr(self.parent_instance, self.to_field)
435        else:
436            orig = self.parent_instance.pk
437        if str(value) != str(orig):
438            raise ValidationError(
439                self.error_messages["invalid_choice"], code="invalid_choice"
440            )
441        return self.parent_instance
442
443    def has_changed(self, initial, data):
444        return False
445
446
447class ModelChoiceIteratorValue:
448    def __init__(self, value, instance):
449        self.value = value
450        self.instance = instance
451
452    def __str__(self):
453        return str(self.value)
454
455    def __hash__(self):
456        return hash(self.value)
457
458    def __eq__(self, other):
459        if isinstance(other, ModelChoiceIteratorValue):
460            other = other.value
461        return self.value == other
462
463
464class ModelChoiceIterator:
465    def __init__(self, field):
466        self.field = field
467        self.queryset = field.queryset
468
469    def __iter__(self):
470        if self.field.empty_label is not None:
471            yield ("", self.field.empty_label)
472        queryset = self.queryset
473        # Can't use iterator() when queryset uses prefetch_related()
474        if not queryset._prefetch_related_lookups:
475            queryset = queryset.iterator()
476        for obj in queryset:
477            yield self.choice(obj)
478
479    def __len__(self):
480        # count() adds a query but uses less memory since the QuerySet results
481        # won't be cached. In most cases, the choices will only be iterated on,
482        # and __len__() won't be called.
483        return self.queryset.count() + (1 if self.field.empty_label is not None else 0)
484
485    def __bool__(self):
486        return self.field.empty_label is not None or self.queryset.exists()
487
488    def choice(self, obj):
489        return (
490            ModelChoiceIteratorValue(self.field.prepare_value(obj), obj),
491            str(obj),
492        )
493
494
495class ModelChoiceField(ChoiceField):
496    """A ChoiceField whose choices are a model QuerySet."""
497
498    # This class is a subclass of ChoiceField for purity, but it doesn't
499    # actually use any of ChoiceField's implementation.
500    default_error_messages = {
501        "invalid_choice": "Select a valid choice. That choice is not one of the available choices.",
502    }
503    iterator = ModelChoiceIterator
504
505    def __init__(
506        self,
507        queryset,
508        *,
509        empty_label="---------",
510        required=True,
511        initial=None,
512        to_field_name=None,
513        **kwargs,
514    ):
515        # Call Field instead of ChoiceField __init__() because we don't need
516        # ChoiceField.__init__().
517        Field.__init__(
518            self,
519            required=required,
520            initial=initial,
521            **kwargs,
522        )
523        if required and initial is not None:
524            self.empty_label = None
525        else:
526            self.empty_label = empty_label
527        self.queryset = queryset
528        self.to_field_name = to_field_name
529
530    def __deepcopy__(self, memo):
531        result = super(ChoiceField, self).__deepcopy__(memo)
532        # Need to force a new ModelChoiceIterator to be created, bug #11183
533        if self.queryset is not None:
534            result.queryset = self.queryset.all()
535        return result
536
537    def _get_queryset(self):
538        return self._queryset
539
540    def _set_queryset(self, queryset):
541        self._queryset = None if queryset is None else queryset.all()
542
543    queryset = property(_get_queryset, _set_queryset)
544
545    def _get_choices(self):
546        # If self._choices is set, then somebody must have manually set
547        # the property self.choices. In this case, just return self._choices.
548        if hasattr(self, "_choices"):
549            return self._choices
550
551        # Otherwise, execute the QuerySet in self.queryset to determine the
552        # choices dynamically. Return a fresh ModelChoiceIterator that has not been
553        # consumed. Note that we're instantiating a new ModelChoiceIterator *each*
554        # time _get_choices() is called (and, thus, each time self.choices is
555        # accessed) so that we can ensure the QuerySet has not been consumed. This
556        # construct might look complicated but it allows for lazy evaluation of
557        # the queryset.
558        return self.iterator(self)
559
560    choices = property(_get_choices, ChoiceField._set_choices)
561
562    def prepare_value(self, value):
563        if hasattr(value, "_meta"):
564            if self.to_field_name:
565                return value.serializable_value(self.to_field_name)
566            else:
567                return value.pk
568        return super().prepare_value(value)
569
570    def to_python(self, value):
571        if value in self.empty_values:
572            return None
573        try:
574            key = self.to_field_name or "pk"
575            if isinstance(value, self.queryset.model):
576                value = getattr(value, key)
577            value = self.queryset.get(**{key: value})
578        except (ValueError, TypeError, self.queryset.model.DoesNotExist):
579            raise ValidationError(
580                self.error_messages["invalid_choice"],
581                code="invalid_choice",
582                params={"value": value},
583            )
584        return value
585
586    def validate(self, value):
587        return Field.validate(self, value)
588
589    def has_changed(self, initial, data):
590        initial_value = initial if initial is not None else ""
591        data_value = data if data is not None else ""
592        return str(self.prepare_value(initial_value)) != str(data_value)
593
594
595class ModelMultipleChoiceField(ModelChoiceField):
596    """A MultipleChoiceField whose choices are a model QuerySet."""
597
598    default_error_messages = {
599        "invalid_list": "Enter a list of values.",
600        "invalid_choice": "Select a valid choice. %(value)s is not one of the available choices.",
601        "invalid_pk_value": "โ€œ%(pk)sโ€ is not a valid value.",
602    }
603
604    def __init__(self, queryset, **kwargs):
605        super().__init__(queryset, empty_label=None, **kwargs)
606
607    def to_python(self, value):
608        if not value:
609            return []
610        return list(self._check_values(value))
611
612    def clean(self, value):
613        value = self.prepare_value(value)
614        if self.required and not value:
615            raise ValidationError(self.error_messages["required"], code="required")
616        elif not self.required and not value:
617            return self.queryset.none()
618        if not isinstance(value, list | tuple):
619            raise ValidationError(
620                self.error_messages["invalid_list"],
621                code="invalid_list",
622            )
623        qs = self._check_values(value)
624        # Since this overrides the inherited ModelChoiceField.clean
625        # we run custom validators here
626        self.run_validators(value)
627        return qs
628
629    def _check_values(self, value):
630        """
631        Given a list of possible PK values, return a QuerySet of the
632        corresponding objects. Raise a ValidationError if a given value is
633        invalid (not a valid PK, not in the queryset, etc.)
634        """
635        key = self.to_field_name or "pk"
636        # deduplicate given values to avoid creating many querysets or
637        # requiring the database backend deduplicate efficiently.
638        try:
639            value = frozenset(value)
640        except TypeError:
641            # list of lists isn't hashable, for example
642            raise ValidationError(
643                self.error_messages["invalid_list"],
644                code="invalid_list",
645            )
646        for pk in value:
647            try:
648                self.queryset.filter(**{key: pk})
649            except (ValueError, TypeError):
650                raise ValidationError(
651                    self.error_messages["invalid_pk_value"],
652                    code="invalid_pk_value",
653                    params={"pk": pk},
654                )
655        qs = self.queryset.filter(**{f"{key}__in": value})
656        pks = {str(getattr(o, key)) for o in qs}
657        for val in value:
658            if str(val) not in pks:
659                raise ValidationError(
660                    self.error_messages["invalid_choice"],
661                    code="invalid_choice",
662                    params={"value": val},
663                )
664        return qs
665
666    def prepare_value(self, value):
667        if (
668            hasattr(value, "__iter__")
669            and not isinstance(value, str)
670            and not hasattr(value, "_meta")
671        ):
672            prepare_value = super().prepare_value
673            return [prepare_value(v) for v in value]
674        return super().prepare_value(value)
675
676    def has_changed(self, initial, data):
677        if initial is None:
678            initial = []
679        if data is None:
680            data = []
681        if len(initial) != len(data):
682            return True
683        initial_set = {str(value) for value in self.prepare_value(initial)}
684        data_set = {str(value) for value in data}
685        return data_set != initial_set
686
687    def value_from_form_data(self, data, files, html_name):
688        return data.getlist(html_name)
689
690
691def modelfield_to_formfield(
692    modelfield, form_class=None, choices_form_class=None, **kwargs
693):
694    defaults = {
695        "required": modelfield.required,
696    }
697
698    if modelfield.has_default():
699        defaults["initial"] = modelfield.get_default()
700
701    if modelfield.choices is not None:
702        # Fields with choices get special treatment.
703        include_blank = not modelfield.required or not (
704            modelfield.has_default() or "initial" in kwargs
705        )
706        defaults["choices"] = modelfield.get_choices(include_blank=include_blank)
707        defaults["coerce"] = modelfield.to_python
708        if modelfield.allow_null:
709            defaults["empty_value"] = None
710        if choices_form_class is not None:
711            form_class = choices_form_class
712        else:
713            form_class = fields.TypedChoiceField
714        # Many of the subclass-specific formfield arguments (min_value,
715        # max_value) don't apply for choice fields, so be sure to only pass
716        # the values that TypedChoiceField will understand.
717        for k in list(kwargs):
718            if k not in (
719                "coerce",
720                "empty_value",
721                "choices",
722                "required",
723                "initial",
724                "error_messages",
725            ):
726                del kwargs[k]
727
728    defaults.update(kwargs)
729
730    if form_class is not None:
731        return form_class(**defaults)
732
733    # Avoid a circular import
734    from plain import models
735
736    # Auto fields aren't rendered by default
737    if isinstance(modelfield, models.AutoField) or issubclass(
738        modelfield.__class__, models.AutoField
739    ):
740        return None
741
742    if isinstance(modelfield, models.BooleanField):
743        form_class = (
744            fields.NullBooleanField if modelfield.allow_null else fields.BooleanField
745        )
746        # In HTML checkboxes, 'required' means "must be checked" which is
747        # different from the choices case ("must select some value").
748        # required=False allows unchecked checkboxes.
749        defaults["required"] = False
750        return form_class(**defaults)
751
752    if isinstance(modelfield, models.DecimalField):
753        return fields.DecimalField(
754            max_digits=modelfield.max_digits,
755            decimal_places=modelfield.decimal_places,
756            **defaults,
757        )
758
759    if issubclass(modelfield.__class__, models.fields.PositiveIntegerRelDbTypeMixin):
760        return fields.IntegerField(min_value=0, **defaults)
761
762    if isinstance(modelfield, models.TextField):
763        # Passing max_length to fields.CharField means that the value's length
764        # will be validated twice. This is considered acceptable since we want
765        # the value in the form field (to pass into widget for example).
766        return fields.CharField(max_length=modelfield.max_length, **defaults)
767
768    if isinstance(modelfield, models.CharField):
769        # Passing max_length to forms.CharField means that the value's length
770        # will be validated twice. This is considered acceptable since we want
771        # the value in the form field (to pass into widget for example).
772        # TODO: Handle multiple backends with different feature flags.
773        from plain.models.db import connection
774
775        if (
776            modelfield.allow_null
777            and not connection.features.interprets_empty_strings_as_nulls
778        ):
779            defaults["empty_value"] = None
780        return fields.CharField(
781            max_length=modelfield.max_length,
782            **defaults,
783        )
784
785    if isinstance(modelfield, models.JSONField):
786        return fields.JSONField(
787            encoder=modelfield.encoder, decoder=modelfield.decoder, **defaults
788        )
789
790    if isinstance(modelfield, models.ForeignKey):
791        return ModelChoiceField(
792            queryset=modelfield.remote_field.model._default_manager,
793            to_field_name=modelfield.remote_field.field_name,
794            **defaults,
795        )
796
797    # TODO related (OneToOne, m2m)
798
799    # If there's a form field of the exact same name, use it
800    # (models.URLField -> forms.URLField)
801    if hasattr(fields, modelfield.__class__.__name__):
802        form_class = getattr(fields, modelfield.__class__.__name__)
803        return form_class(**defaults)
804
805    # Default to CharField if we didn't find anything else
806    return fields.CharField(**defaults)