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