Forms reference#
Module summary#
next.forms exposes form base classes, the @action decorator,
formset helpers, frozen field and form specs,
and a curated set of commonly used Django form fields and widgets.
Any public django.forms name is also importable from next.forms,
see Fields and widgets for the contract.
API tiers#
The forms surface splits into tiers that describe the intended audience for each name. The lists below are representative. The autodoc blocks under Public API are the exhaustive surface.
- Stable.
Form,ModelForm,BaseForm,BaseModelForm,@action,redirect_to_origin,FormWizard,DForm,FormActionNotFoundError, andautodiscover_forms. Use these in application code. Form classes self-register, so reach for@actiononly for form-less handlers.- Advanced.
FormActionBackend,RegistryFormActionBackend,ActionOutcome,ActionOutcomeKind,ActionRegistration,ActionGuard,ComponentWidget,FormWizardBackend,SessionFormWizardBackend,CacheFormWizardBackend, the frozen specs (FieldSpec,FormsetSpec,FormSpec,FormSectionSpec,FormsetRowSpec,FieldKind), the spec helpers (field_spec,form_spec,formset_spec), the formset helpercleanup_extra_initial, the origin helpers (OriginMatch,resolve_origin,resolve_url_to_match,resolve_url_to_page), thePermissionOutcometype alias for the dynamic permission hooks, and thesignalsandcheckssubmodules. Use these when writing a custom backend or a form renderer.- Framework machinery.
The wiring lives on the owning submodules and is not re-exported at the package level.
FormActionDispatchlives innext.forms.dispatch.FormActionManager, theform_action_managerinstance, andbuild_form_namespace_for_actionlive innext.forms.manager.ActionMeta,file_to_dotted_module,scope_key_for,build_action_guard, andrecord_possible_collisionlive innext.forms.backends. Thewizard_backend_managerinstance lives innext.forms.wizard.FormProviderandCleanedDataProviderlive innext.forms.markers.bind_component_widgetslives innext.forms.widgets.render_form_page_with_errorslives innext.forms.rendering.RegistrationDiagnosticsand theregistration_diagnosticsinstance live innext.forms.diagnostics. The UID helpersFORM_ACTION_REVERSE_NAME,URL_NAME_FORM_ACTION,ORIGIN_FIELD_NAME,FORM_ORIGIN_OVERRIDE_KEY,reverse_form_action, andvalidated_origin_pathlive innext.forms.uid. The test isolation helperreset_form_registration_statebelongs tonext.testing, documented under Testing reference.- Internal hooks.
Underscore-prefixed helpers inside the submodules, such as the form-building functions in
next.forms.dispatch, are implementation details.next.forms.__all__is the source of truth for the curated package surface and exports no underscore names. Do not import underscore names in application code.
Public API#
Autodiscover#
The helper wraps Django’s autodiscover_modules(), so re-runs are no-ops while Python caches the imported modules.
The test isolation helper next.testing.reset_form_registration_state() clears the registries and the registration diagnostics, see Testing reference.
Decorator#
- next.forms.action(name: C, /) C[source]#
- next.forms.action(name: str | None = None, *, form_class: type[Form] | Callable[[...], Any] | None = None, scope: str | None = None, login_required: bool = False, permission_required: str | Iterable[str] | None = None) Callable[[C], C]
Register a callable as a named form action.
Used bare or with no name the action is registered under the function’s own name. form_class accepts a factory callable or a Form class that does not register its own endpoint. scope overrides the file-based scope with ‘page’ or ‘shared’. login_required and permission_required guard the endpoint before origin resolution, get_initial, and form binding, so no application code runs for a denied request.
Exceptions#
FormActionNotFoundError is raised when no registered action matches a requested name.
FormActionManager.get_action_url, the {% form %} and {% action_url %} tags, and the testing helpers resolve_action_url and build_form_for all raise it.
It subclasses LookupError and carries the failing name, the page_path that was searched, the close-match suggestions tuple, and the registry_empty flag.
Every raising surface renders the suggestions into the message as Closest matches: 'x', 'y', computed by close-match comparison against the registered names.
The comparison and the message run on first render, so probing for an action by catching the exception costs no close-match work.
When registry_empty is true the message also explains that no actions are registered at all and points at autodiscovery.
- exception next.forms.FormActionNotFoundError(message: str | None = None, *, name: str = '', page_path: str | None = None, candidates: Callable[[], Iterable[str]] | Iterable[str] = (), registry_empty: bool = False)[source]#
No registered form action matches the requested name.
- __init__(message: str | None = None, *, name: str = '', page_path: str | None = None, candidates: Callable[[], Iterable[str]] | Iterable[str] = (), registry_empty: bool = False) None[source]#
Store the lookup context, deferring close-match work until rendered.
Form base classes#
check_permissions and has_object_permission are the opt-in dynamic permission hooks.
Both return PermissionOutcome, the bool | HttpResponse | None alias re-exported from next.forms.
See Dynamic permission hooks for the authoring contract and the ordering against the static guard.
- class next.forms.Form(data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=<class 'django.forms.utils.ErrorList'>, label_suffix=None, empty_permitted=False, field_order=None, use_required_attribute=None, renderer=None, bound_field_class=None)[source]#
A collection of fields with get_initial and on_valid support.
- base_fields = {}#
- declared_fields = {}#
- property media#
Return all media required to render the widgets on this form.
- class next.forms.ModelForm(data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=<class 'django.forms.utils.ErrorList'>, label_suffix=None, empty_permitted=False, instance=None, use_required_attribute=None, renderer=None)[source]#
Form for editing a model instance with get_initial and on_valid support.
- base_fields = {}#
- declared_fields = {}#
- property media#
Return all media required to render the widgets on this form.
- class next.forms.BaseForm(data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=<class 'django.forms.utils.ErrorList'>, label_suffix=None, empty_permitted=False, field_order=None, use_required_attribute=None, renderer=None, bound_field_class=None)[source]#
Custom BaseForm extended with get_initial and on_valid.
- default_renderer = <next.forms.base._DivFormRenderer object>#
- classmethod __init_subclass__(**kwargs) None[source]#
Register subclass in form_action_manager automatically.
- class next.forms.BaseModelForm(data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=<class 'django.forms.utils.ErrorList'>, label_suffix=None, empty_permitted=False, instance=None, use_required_attribute=None, renderer=None)[source]#
Custom BaseModelForm with get_initial and on_valid support.
- default_renderer = <next.forms.base._DivFormRenderer object>#
- classmethod __init_subclass__(**kwargs) None[source]#
Register subclass in form_action_manager automatically.
- classmethod get_initial(**url_kwargs) dict[str, Any] | Model[source]#
Return a model instance loaded from the URL, or an empty dict.
Form wizard#
FormWizard routes a sequence of step forms across requests.
FormWizardBackend is the draft-persistence contract, SessionFormWizardBackend is the bundled default, and CacheFormWizardBackend is the cache-backed alternative.
The wizard check_permissions classmethod is the view-level dynamic permission hook, enforced per step POST.
See Form wizards and Wizard backend for the topic guides.
- class next.forms.FormWizard(request: HttpRequest, url_kwargs: dict[str, object] | None = None, base_path: str | None = None)[source]#
Routes a sequence of forms across requests and finalises on the last step.
- classmethod __init_subclass__(**kwargs) None[source]#
Register the wizard subclass automatically and stamp the hook flag.
- classmethod check_permissions() PermissionOutcome[source]#
View-level gate per step POST, DI-resolved. None or True allows.
- __init__(request: HttpRequest, url_kwargs: dict[str, object] | None = None, base_path: str | None = None) None[source]#
Bind the wizard to a request, its URL kwargs, and a page path.
- get_steps() list[tuple[str, type[DjangoForm]]][source]#
Return the step list. Override for conditional steps.
- get_form_kwargs(step: str | None = None) dict[str, Any][source]#
Return extra kwargs for the step form. Override for cross-step inputs.
- get_cleaned_data_for_step(step: str) dict[str, Any] | None[source]#
Return the stored cleaned data for step, or None when not stored.
- first_incomplete_step() str | None[source]#
Return the first step without stored data, or None when all are stored.
- save_step(step: str, data: dict[str, Any]) None[source]#
Persist cleaned data for one step through the backend.
Any already-loaded mapping is updated in place instead of being invalidated, so the request avoids a reload round-trip after a save and sibling instances sharing the request memo see the save.
- current_step() str[source]#
Return the active step from the URL kwarg, defaulting to the first.
URL kwargs that exist but lack the Meta.url_param key signal a route whose step segment is named differently, which would pin the wizard to its first step forever, so that misconfiguration raises instead of falling back.
- next_step(step: str | None = None) str | None[source]#
Return the step following step (or the current step), or None.
- step_form_class(step: str | None = None) type[DjangoForm] | None[source]#
Return the form class registered for step (or the current step).
- current_form() DjangoForm | None[source]#
Return an unbound form for the current step, prefilled from storage.
- template_namespace() SimpleNamespace[source]#
Return the {form, wizard} namespace consumed by the form tag.
- class next.forms.FormWizardBackend[source]#
Persists FormWizard step drafts between requests, keyed by storage id.
- abstractmethod load(request: HttpRequest, storage_id: str) dict[str, Any][source]#
Return the {step: cleaned_data} mapping for the wizard, in step order.
- abstractmethod save_step(request: HttpRequest, storage_id: str, step: str, data: dict[str, Any]) None[source]#
Persist cleaned data for a single step.
Implementations must persist data so a later load returns an equivalent mapping for the step. The wizard write-through cache assumes saved data round-trips verbatim.
- class next.forms.SessionFormWizardBackend(_config: dict[str, Any] | None = None)[source]#
Stores drafts in the request session with a typed value codec.
- __init__(_config: dict[str, Any] | None = None) None[source]#
Accept the backend config mapping for factory parity.
- load(request: HttpRequest, storage_id: str) dict[str, Any][source]#
Return the decoded {step: cleaned_data} mapping for the visitor.
- class next.forms.CacheFormWizardBackend(config: dict[str, Any] | None = None)[source]#
Stores drafts in the Django cache, namespaced by session and storage id.
- __init__(config: dict[str, Any] | None = None) None[source]#
Read CACHE_ALIAS and TIMEOUT from the backend OPTIONS.
- load(request: HttpRequest, storage_id: str) dict[str, Any][source]#
Return the cached {step: cleaned_data} mapping for the visitor.
wizard_backend_manager in next.forms.wizard is the lazy holder for the single
configured wizard backend, a SingleBackendManager bound to FORM_WIZARD_BACKEND.
It reads the setting on first use and caches the result.
A malformed entry, an unimportable path, or a class outside the FormWizardBackend
family raises ImproperlyConfigured out of get, since a family with one backend
has nothing to fall back to.
- class next.backends.SingleBackendManager(setting: str, *, base: BackendRoot, default: str | None = None)[source]
Instantiates the single backend named by one framework settings key.
A misconfigured entry raises out of get() rather than being logged and skipped, because a family with one backend has nothing to fall back to.
- __init__(setting: str, *, base: BackendRoot, default: str | None = None) None[source]
Bind the manager to a settings key without reading it.
- get() T[source]
Return the configured backend, building it on first use.
The canonical entry for the class lives in Backends reference.
Fields and widgets#
The framework re-exports a curated set of commonly used Django form fields and widgets through
next.forms so a single import covers most form definitions.
The package surface is a superset of django.forms by construction.
Any public django.forms
name resolves through next.forms, with the framework versions winning where next.dj overrides
a name such as Form or ModelForm, and every other name resolving to the Django original.
The factories formset_factory, modelformset_factory, inlineformset_factory, and
modelform_factory plus BoundField are re-exported statically so type checkers see them,
the rest of the passthrough resolves at runtime through the module __getattr__.
The submodules next.forms.widgets and next.forms.formsets carry the same passthrough for
the public names of django.forms.widgets and django.forms.formsets respectively.
Base form classes and auto-registration machinery for next.forms.
- class next.forms.base.BooleanField(*, required=True, widget=None, label=None, initial=None, help_text='', error_messages=None, show_hidden_initial=False, validators=(), localize=False, disabled=False, label_suffix=None, template_name=None, bound_field_class=None)[source]#
- widget#
alias of
CheckboxInput
- class next.forms.base.CharField(*, max_length=None, min_length=None, strip=True, empty_value='', **kwargs)[source]#
- class next.forms.base.CheckboxInput(attrs=None, check_test=None)[source]#
- input_type = 'checkbox'#
- template_name = 'django/forms/widgets/checkbox.html'#
- value_from_datadict(data, files, name)[source]#
Given a dictionary of data and this widget’s name, return the value of this widget or None if it’s not provided.
- property media#
- class next.forms.base.CheckboxSelectMultiple(attrs=None, choices=())[source]#
- allow_multiple_selected = True#
- input_type = 'checkbox'#
- template_name = 'django/forms/widgets/checkbox_select.html'#
- option_template_name = 'django/forms/widgets/checkbox_option.html'#
- property media#
- class next.forms.base.ChoiceField(*, choices=(), **kwargs)[source]#
-
- default_error_messages = {'invalid_choice': 'Select a valid choice. %(value)s is not one of the available choices.'}#
- property choices#
- class next.forms.base.ClearableFileInput(attrs=None)[source]#
- clear_checkbox_label = 'Clear'#
- initial_text = 'Currently'#
- input_text = 'Change'#
- template_name = 'django/forms/widgets/clearable_file_input.html'#
- checked = False#
- use_fieldset = False#
- clear_checkbox_name(name)[source]#
Given the name of the file input, return the name of the clear checkbox input.
- clear_checkbox_id(name)[source]#
Given the name of the clear checkbox input, return the HTML id for it.
- property media#
- class next.forms.base.DateField(*, input_formats=None, **kwargs)[source]#
-
- input_formats = ['%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', '%b %d %Y', '%b %d, %Y', '%d %b %Y', '%d %b, %Y', '%B %d %Y', '%B %d, %Y', '%d %B %Y', '%d %B, %Y']#
- default_error_messages = {'invalid': 'Enter a valid date.'}#
- class next.forms.base.DateInput(attrs=None, format=None)[source]#
- format_key = 'DATE_INPUT_FORMATS'#
- template_name = 'django/forms/widgets/date.html'#
- property media#
- class next.forms.base.DateTimeField(*, input_formats=None, **kwargs)[source]#
- widget#
alias of
DateTimeInput
- input_formats = <django.forms.fields.DateTimeFormatsIterator object>#
- default_error_messages = {'invalid': 'Enter a valid date/time.'}#
- class next.forms.base.DateTimeInput(attrs=None, format=None)[source]#
- format_key = 'DATETIME_INPUT_FORMATS'#
- template_name = 'django/forms/widgets/datetime.html'#
- property media#
- class next.forms.base.DecimalField(*, max_value=None, min_value=None, max_digits=None, decimal_places=None, **kwargs)[source]#
- default_error_messages = {'invalid': 'Enter a number.'}#
- __init__(*, max_value=None, min_value=None, max_digits=None, decimal_places=None, **kwargs)[source]#
- class next.forms.base.DurationField(*, required=True, widget=None, label=None, initial=None, help_text='', error_messages=None, show_hidden_initial=False, validators=(), localize=False, disabled=False, label_suffix=None, template_name=None, bound_field_class=None)[source]#
- default_error_messages = {'invalid': 'Enter a valid duration.', 'overflow': 'The number of days must be between {min_days} and {max_days}.'}#
- class next.forms.base.EmailField(**kwargs)[source]#
- widget#
alias of
EmailInput
- default_validators = [<django.core.validators.EmailValidator object>]#
- class next.forms.base.EmailInput(attrs=None)[source]#
- input_type = 'email'#
- template_name = 'django/forms/widgets/email.html'#
- property media#
- class next.forms.base.FileField(*, max_length=None, allow_empty_file=False, **kwargs)[source]#
- widget#
alias of
ClearableFileInput
- default_error_messages = {'contradiction': 'Please either submit a file or check the clear checkbox, not both.', 'empty': 'The submitted file is empty.', 'invalid': 'No file was submitted. Check the encoding type on the form.', 'max_length': '', 'missing': 'No file was submitted.'}#
- clean(data, initial=None)[source]#
Validate the given value and return its “cleaned” value as an appropriate Python object. Raise ValidationError for any errors.
- class next.forms.base.FileInput(attrs=None)[source]#
- allow_multiple_selected = False#
- input_type = 'file'#
- needs_multipart_form = True#
- template_name = 'django/forms/widgets/file.html'#
- property media#
- class next.forms.base.FloatField(*, max_value=None, min_value=None, step_size=None, **kwargs)[source]#
- default_error_messages = {'invalid': 'Enter a number.'}#
- class next.forms.base.HiddenInput(attrs=None)[source]#
- input_type = 'hidden'#
- template_name = 'django/forms/widgets/hidden.html'#
- property media#
- class next.forms.base.ImageField(*, max_length=None, allow_empty_file=False, **kwargs)[source]#
- default_validators = [<function validate_image_file_extension>]#
- default_error_messages = {'invalid_image': 'Upload a valid image. The file you uploaded was either not an image or a corrupted image.'}#
- class next.forms.base.IntegerField(*, max_value=None, min_value=None, step_size=None, **kwargs)[source]#
- widget#
alias of
NumberInput
- default_error_messages = {'invalid': 'Enter a whole number.'}#
- re_decimal = <SimpleLazyObject: re.compile('\\.0*\\s*$')>#
- class next.forms.base.JSONField(encoder=None, decoder=None, **kwargs)[source]#
- default_error_messages = {'invalid': 'Enter a valid JSON.'}#
- class next.forms.base.ModelChoiceField(queryset, *, empty_label='---------', required=True, widget=None, label=None, initial=None, help_text='', to_field_name=None, limit_choices_to=None, blank=False, **kwargs)[source]#
A ChoiceField whose choices are a model QuerySet.
- default_error_messages = {'invalid_choice': 'Select a valid choice. That choice is not one of the available choices.'}#
- iterator#
alias of
ModelChoiceIterator
- __init__(queryset, *, empty_label='---------', required=True, widget=None, label=None, initial=None, help_text='', to_field_name=None, limit_choices_to=None, blank=False, **kwargs)[source]#
- get_limit_choices_to()[source]#
Return
limit_choices_tofor this form field.If it is a callable, invoke it and return the result.
- property queryset#
- label_from_instance(obj)[source]#
Convert objects into strings and generate the labels for the choices presented by this object. Subclasses can override this method to customize the display of the choices.
- property choices#
- class next.forms.base.ModelMultipleChoiceField(queryset, **kwargs)[source]#
A MultipleChoiceField whose choices are a model QuerySet.
- widget#
alias of
SelectMultiple
alias of
MultipleHiddenInput
- default_error_messages = {'invalid_choice': 'Select a valid choice. %(value)s is not one of the available choices.', 'invalid_list': 'Enter a list of values.', 'invalid_pk_value': '“%(pk)s” is not a valid value.'}#
- class next.forms.base.MultipleChoiceField(*, choices=(), **kwargs)[source]#
alias of
MultipleHiddenInput
- widget#
alias of
SelectMultiple
- default_error_messages = {'invalid_choice': 'Select a valid choice. %(value)s is not one of the available choices.', 'invalid_list': 'Enter a list of values.'}#
- class next.forms.base.NumberInput(attrs=None)[source]#
- input_type = 'number'#
- template_name = 'django/forms/widgets/number.html'#
- property media#
- class next.forms.base.PasswordInput(attrs=None, render_value=False)[source]#
- input_type = 'password'#
- template_name = 'django/forms/widgets/password.html'#
- property media#
- class next.forms.base.RadioSelect(attrs=None, choices=())[source]#
- input_type = 'radio'#
- template_name = 'django/forms/widgets/radio.html'#
- option_template_name = 'django/forms/widgets/radio_option.html'#
- use_fieldset = True#
- id_for_label(id_, index=None)[source]#
Don’t include for=”field_0” in <label> to improve accessibility when using a screen reader, in addition clicking such a label would toggle the first input.
- property media#
- class next.forms.base.RegexField(regex, **kwargs)[source]#
- __init__(regex, **kwargs)[source]#
regex can be either a string or a compiled regular expression object.
- property regex#
- class next.forms.base.Select(attrs=None, choices=())[source]#
- input_type = 'select'#
- template_name = 'django/forms/widgets/select.html'#
- option_template_name = 'django/forms/widgets/select_option.html'#
- add_id_index = False#
- checked_attribute = {'selected': True}#
- option_inherits_attrs = False#
- use_required_attribute(initial)[source]#
Don’t render ‘required’ if the first <option> has a value, as that’s invalid HTML.
- property media#
- class next.forms.base.SelectMultiple(attrs=None, choices=())[source]#
- allow_multiple_selected = True#
- value_from_datadict(data, files, name)[source]#
Given a dictionary of data and this widget’s name, return the value of this widget or None if it’s not provided.
- property media#
- class next.forms.base.SlugField(*, allow_unicode=False, **kwargs)[source]#
-
- default_validators = [<django.core.validators.RegexValidator object>]#
- class next.forms.base.TextInput(attrs=None)[source]#
- input_type = 'text'#
- template_name = 'django/forms/widgets/text.html'#
- property media#
- class next.forms.base.Textarea(attrs=None)[source]#
- template_name = 'django/forms/widgets/textarea.html'#
- property media#
- class next.forms.base.TimeField(*, input_formats=None, **kwargs)[source]#
-
- input_formats = ['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']#
- default_error_messages = {'invalid': 'Enter a valid time.'}#
- class next.forms.base.TimeInput(attrs=None, format=None)[source]#
- format_key = 'TIME_INPUT_FORMATS'#
- template_name = 'django/forms/widgets/time.html'#
- property media#
- class next.forms.base.TypedChoiceField(*, coerce=<function TypedChoiceField.<lambda>>, empty_value='', **kwargs)[source]#
- class next.forms.base.URLField(*, assume_scheme=None, **kwargs)[source]#
-
- default_error_messages = {'invalid': 'Enter a valid URL.'}#
- default_validators = [<django.core.validators.URLValidator object>]#
- class next.forms.base.URLInput(attrs=None)[source]#
- input_type = 'url'#
- template_name = 'django/forms/widgets/url.html'#
- property media#
- class next.forms.base.UUIDField(*, max_length=None, min_length=None, strip=True, empty_value='', **kwargs)[source]#
- default_error_messages = {'invalid': 'Enter a valid UUID.'}#
- exception next.forms.base.ValidationError(message, code=None, params=None)[source]#
An error while validating data.
- __init__(message, code=None, params=None)[source]#
The message argument can be a single error, a list of errors, or a dictionary that maps field names to lists of errors. What we define as an “error” can be either a simple string or an instance of ValidationError with its message attribute set, and what we define as list or dictionary can be an actual list or dict or an instance of ValidationError with its error_list or error_dict attribute set.
- property message_dict#
- property messages#
- class next.forms.base.Widget(attrs=None)[source]#
- needs_multipart_form = False#
- is_localized = False#
- is_required = False#
- supports_microseconds = True#
- use_fieldset = False#
- value_from_datadict(data, files, name)[source]#
Given a dictionary of data and this widget’s name, return the value of this widget or None if it’s not provided.
- id_for_label(id_)[source]#
Return the HTML ID attribute of this Widget for use by a <label>, given the ID of the field. Return an empty string if no ID is available.
This hook is necessary because some widgets have multiple HTML elements and, thus, multiple IDs. In that case, this method should return an ID value that corresponds to the first ID in the widget’s tags.
- property media#
ComponentWidget renders a field through a registered next.dj component instead of a Django widget template.
See Field components for the topic guide.
- class next.forms.ComponentWidget(component_name: str, *, attrs: dict[str, Any] | None = None, **component_kwargs)[source]#
A form widget that renders a registered next-component.
- __init__(component_name: str, *, attrs: dict[str, Any] | None = None, **component_kwargs) None[source]#
Store the target component name and its extra render kwargs.
- render(name: str, value: object, attrs: dict[str, Any] | None = None, renderer: BaseRenderer | None = None) SafeString[source]#
Resolve the component within scope and render it to HTML.
- property media#
bind_component_widgets injects the page scope path, the live request, the static collector,
and optionally the field errors onto every ComponentWidget of a form before rendering.
The {% form %} tag calls it, so application code needs it only when rendering a component-widget
form outside the tag.
It accepts a form or a formset and binds every member form of a formset, which is how formset
rendering through {% form %} carries component widgets.
It imports from next.forms.widgets directly.
- next.forms.widgets.bind_component_widgets(form: BaseForm | BaseFormSet, *, template_path: str | Path | None, request: HttpRequest | None = None, collector: StaticCollector | None = None, with_errors: bool = False) None[source]#
Inject scope path, request, collector, and field errors onto ComponentWidgets.
A formset has no fields of its own, so each of its member forms is bound instead.
Markers#
DForm is re-exported from next.forms.
The provider classes import from next.forms.markers directly.
FormProvider auto-registers through the __init_subclass__ hook on RegisteredParameterProvider and resolves the bound form parameter, so application code never instantiates it.
Dependency injection markers and provider for form parameters.
- class next.forms.markers.CleanedDataProvider[source]#
Inject merged wizard cleaned data for the parameter named cleaned_data.
- priority = 40#
- can_handle(param: Parameter, context: ResolutionContext) bool[source]#
Return True when context carries cleaned data and the name matches.
- resolve(_param: Parameter, context: ResolutionContext) object[source]#
Return the cleaned data mapping from context.
- class next.forms.markers.DForm[source]#
Annotation for injecting a form instance by class.
Use as DForm[MyForm] or DForm[“MyForm”].
- class next.forms.markers.FormProvider[source]#
Inject a form instance matching the annotation or the parameter name form.
- priority = 40#
- can_handle(param: Parameter, context: ResolutionContext) bool[source]#
Return True when context carries a form compatible with param.
- resolve(_param: Parameter, context: ResolutionContext) object[source]#
Return the form instance from context.
Dispatch#
FormActionDispatch, ActionOutcome, and ActionOutcomeKind are the public members of this module.
ActionOutcome and ActionOutcomeKind are re-exported from next.forms, while FormActionDispatch imports from next.forms.dispatch directly.
ActionOutcome is the frozen keyword-only dataclass a backend’s shape_response hook receives, with ActionOutcomeKind as its kind discriminator.
On INVALID outcomes the page_path and origin fields carry the resolved identity of the origin page.
The page_path field holds the source location of its page.py and the origin field holds the validated origin URL path.
FormActionDispatch.shape_response builds the default envelope for one outcome, and the backend hook delegates to it unless overridden.
ensure_http_response coerces a handler return value into an HttpResponse, kept for custom backends that drive the pipeline by hand.
The underscore-prefixed helpers are internal hooks per the Internal hooks tier described above.
POST dispatch pipeline for form actions.
- class next.forms.dispatch.ActionOutcome(*, kind: ActionOutcomeKind, action_name: str, uid: str | None = None, raw: Any = None, form: django_forms.Form | None = None, redirect_to: str | None = None, url_kwargs: dict[str, object] | None = None, wizard: FormWizard | None = None, page_path: Path | None = None, origin: str | None = None)[source]#
One pipeline decision waiting to be shaped into an HTTP response.
Fields may be added in future versions, construct with keywords only.
- kind: ActionOutcomeKind#
- wizard: FormWizard | None#
- __init__(*, kind: ActionOutcomeKind, action_name: str, uid: str | None = None, raw: Any = None, form: django_forms.Form | None = None, redirect_to: str | None = None, url_kwargs: dict[str, object] | None = None, wizard: FormWizard | None = None, page_path: Path | None = None, origin: str | None = None) None#
- class next.forms.dispatch.ActionOutcomeKind(*values)[source]#
Discriminator for the pipeline outcomes a backend shapes into responses.
- RESULT = 'result'#
- INVALID = 'invalid'#
- WIZARD_ADVANCE = 'wizard_advance'#
- class next.forms.dispatch.FormActionDispatch[source]#
Shared POST pipeline and response shaping for backends.
- static dispatch(backend: FormActionBackend, request: HttpRequest, action_name: str, meta: ActionMeta) HttpResponse[source]#
Validate the form, run the handler, or re-render errors.
- static shape_response(backend: FormActionBackend, request: HttpRequest, outcome: ActionOutcome) HttpResponse[source]#
Build the default envelope for one pipeline outcome.
Invalid submissions re-render the origin page with HTTP 200 and the X-Next-Form/X-Next-Action headers, wizard advances redirect.
Manager#
FormActionManager holds the configured backends behind the module-level form_action_manager instance.
build_form_namespace_for_action builds the {form, wizard} namespace the {% form %} tag consumes, for code rendering that namespace by hand outside the tag.
All three import from next.forms.manager.
FormActionManager.require_action_meta returns the resolved ActionMeta or raises FormActionNotFoundError with close-match suggestions, for callers that cannot proceed without the meta.
reload rebuilds the backends from the current NEXT_FRAMEWORK, dropping the actions registered against the old ones, which is what next.testing.reset_form_actions calls after a settings swap.
version is the cache token the lazy urlpatterns concat keys on, bumped by every registration, registry clear, and reload.
Manager for form action backends and routing.
- class next.forms.manager.FormActionManager(backends: list[FormActionBackend] | None = None)[source]#
Holds one or more backends and yields their URL patterns.
- version: int = 0#
Cache token for the lazy urlpatterns concat. Registrations that bypass the manager and hit a backend directly are not tracked, as they were never supported.
Read it to key a cache of your own on the registered actions. It is a plain attribute rather than a property because the lazy urlpatterns concat reads it on every resolve.
- __init__(backends: list[FormActionBackend] | None = None) None[source]#
Initialise with explicit backends or defer loading to settings.
- reload() None[source]#
Rebuild the backends from the current NEXT_FRAMEWORK settings.
The actions registered against the old backends go with them, so a caller that swaps FORM_ACTION_BACKENDS under a live manager lets the forms register again afterwards.
- register_action(registration: ActionRegistration) None[source]#
Forward registration to the first backend.
- snapshot_actions() ActionsSnapshot[source]#
Capture the actions of every backend for a later restore_actions.
Each token travels back to the backend that minted it, so a list that changed in between restores the backends it still holds.
- restore_actions(snapshot: ActionsSnapshot) None[source]#
Put the captured actions back, moving version as a registration does.
A rollback changes what is registered, so a cache keyed on the token has to see it exactly as it sees a registration.
- get_action_url(action_name: str, *, page_path: str | None = None) str[source]#
Return the reverse URL from the first backend that knows action_name.
- get_action_meta(action_name: str, *, page_path: str | None = None) ActionMeta | None[source]#
Return the action meta from the first backend that knows the name.
- require_action_meta(action_name: str, *, page_path: str | None = None) ActionMeta[source]#
Return the action meta or raise with close matches when none exists.
- property backends: tuple[FormActionBackend, ...]#
Return the configured backends in consultation order.
- property default_backend: FormActionBackend#
Return the first configured backend.
- next.forms.manager.build_form_namespace_for_action(action_name: str, request: HttpRequest, page_path: str | None = None) SimpleNamespace | None[source]#
Build the form namespace used by the form template tag.
- next.forms.manager.resolve_component_anchor(action_name: str, component_path: str) ActionMeta | None[source]#
Return the action meta registered exactly under the component anchor.
An exact anchor hit carries page scope, which tells it apart from the path-independent shared fallback a scoped lookup may return.
Backends#
ActionRegistration is the value object passed to register_action.
It carries the action name, the declaration-site file_path, the scope, the optional access guard, the claims_name_binding flag, and the action target.
The target is one of handler, form_class, or wizard_class, which lets a single register_action call serve the @action decorator, a class-bound form, and a FormWizard.
claims_name_binding defaults to False, which keeps a bare name bound to the registration that claimed it first.
Setting it rebinds the name to this registration, which is what a test override needs to displace an action that is already registered.
snapshot and restore round-trip an opaque state token so a caller can register extra actions and roll the backend back afterwards.
ActionGuard is the frozen access-requirement record built from Meta.login_required and Meta.permission_required or the matching @action keywords.
It is stored under the guard key of ActionMeta and enforced by the dispatch pipeline before the form is built, so custom backends see the declared requirements without extra wiring.
iter_actions yields every stored ActionMeta, including its name key, which is how the forms system checks inspect any configured backend.
ActionMeta and file_to_dotted_module import from next.forms.backends directly.
FormActionManager instantiates one backend per FORM_ACTION_BACKENDS entry, passing the whole config dict to the backend constructor.
scope_key_for derives the registry scope key from a declaration file path and a scope, the same key that partitions actions and wizard storage.
build_action_guard builds an ActionGuard from the declared login_required and permission_required values, or None when both are unset.
record_possible_collision files a name collision into the registration diagnostics when a name is re-registered with a distinct handler, feeding the next.E041 check.
All three import from next.forms.backends directly.
Backend abstractions and in-memory registry for form actions.
- class next.forms.backends.ActionGuard(login_required: bool = False, permissions: tuple[str, ...] = ())[source]#
Access requirements enforced before a form action dispatches.
- class next.forms.backends.ActionMeta[source]#
Per-action data stored in the registry backend.
- wizard_class: type[FormWizard] | None#
- guard: ActionGuard | None#
- class next.forms.backends.ActionRegistration(name: str, file_path: str, scope: str, handler: Callable[..., Any] | None = None, form_class: type[django_forms.Form] | Callable[..., Any] | None = None, wizard_class: type[FormWizard] | None = None, guard: ActionGuard | None = None, claims_name_binding: bool = False)[source]#
A form action to register with its name, declaration site, and target.
Exactly one of handler, form_class, or wizard_class is the action target, except the @action(form_class=…) path which supplies a handler and a form-factory together.
- wizard_class: type[FormWizard] | None = None#
- guard: ActionGuard | None = None#
- claims_name_binding: bool = False#
Whether this registration takes over lookups that carry no page scope.
Registrations are first-wins on a bare name, so an action declared later under an already-registered name stays reachable only through its own page scope. A registration that claims the binding rebinds the name to itself instead, which is how a test override displaces the action it stands in for.
- class next.forms.backends.FormActionBackend[source]#
Storage and HTTP dispatch for @action handlers.
- abstractmethod register_action(registration: ActionRegistration) None[source]#
Record an action from the decorator or __init_subclass__.
Lookups without a page scope resolve a bare name to the first registration that used it, unless a later one sets claims_name_binding and takes the name over.
- abstractmethod get_action_url(action_name: str, *, page_path: str | None = None) str[source]#
Return the reverse URL for action_name.
- abstractmethod dispatch(request: HttpRequest, uid: str) HttpResponse[source]#
Run the handler for uid.
- get_meta(action_name: str, page_path: str | None = None) ActionMeta | None[source]#
Return optional per-action metadata for subclasses.
A lookup with page_path returns the exact page-scoped meta for that path or a shared-scoped fallback, never a page-scoped meta registered under a different path. The template tags rely on this to tell an exact anchor hit apart from the fallback.
- iter_actions() Iterable[ActionMeta][source]#
Yield the metadata of every action this backend owns.
- clear_registry() None[source]#
Drop every action this backend stores, for test isolation.
A backend that keeps no state of its own, because it answers each lookup from its source, has nothing to drop and leaves this alone.
- snapshot() object[source]#
Return an opaque token holding the actions this backend stores.
The token travels back into restore untouched, so a backend picks whatever representation suits its storage. A backend that keeps no state of its own returns None and ignores it again on restore.
- render_invalid_page(request: HttpRequest, action_name: str, form: BaseForm | BaseFormSet | None, page_file_path: Path | None = None, url_kwargs: dict[str, object] | None = None, overrides: dict[str, object] | None = None) str[source]#
Return the full origin-page HTML for a failed validation.
- shape_response(request: HttpRequest, outcome: ActionOutcome) HttpResponse[source]#
Turn one pipeline outcome into the HTTP response.
A partial request routes through shape_partial for a patch envelope. A plain request keeps the default full-page path byte-for-byte.
- exception next.forms.backends.FormActionNotFoundError(message: str | None = None, *, name: str = '', page_path: str | None = None, candidates: Callable[[], Iterable[str]] | Iterable[str] = (), registry_empty: bool = False)[source]#
No registered form action matches the requested name.
- __init__(message: str | None = None, *, name: str = '', page_path: str | None = None, candidates: Callable[[], Iterable[str]] | Iterable[str] = (), registry_empty: bool = False) None[source]#
Store the lookup context, deferring close-match work until rendered.
- class next.forms.backends.RegistryBackendSnapshot(registry: dict[tuple[str, str], ActionMeta], uid_to_name: dict[str, tuple[str, str]], name_index: dict[str, tuple[str, str]])[source]#
An immutable copy of a registry backend’s action maps for rollback.
- class next.forms.backends.RegistryFormActionBackend(_config: dict[str, Any] | None = None)[source]#
In-memory actions behind one dispatcher path keyed by UID.
- __init__(_config: dict[str, Any] | None = None) None[source]#
Create an empty action map. _config is accepted for factory parity.
- clear_registry() None[source]#
Drop every registered action and reset the UID index. For test isolation.
Rebinding beats clearing four populated dicts, and an escaped FormActionNotFoundError keeps its raise-time candidates view.
- snapshot() RegistryBackendSnapshot[source]#
Capture the registered actions so a later restore rolls them back.
A test that registers extra actions takes a snapshot first and restores it afterwards, so a later suite sees the registry exactly as it was without reaching into the backend’s private maps.
- restore(snapshot: object) None[source]#
Restore the registered actions captured by snapshot.
The token is opaque in the contract, so a token this backend never handed out is refused rather than half-applied.
- register_action(registration: ActionRegistration) None[source]#
Store handler, form_class, or wizard_class and a stable uid for the name.
- get_action_url(action_name: str, *, page_path: str | None = None) str[source]#
Return the reverse URL for a registered action name.
- generate_urls() list[URLPattern][source]#
Return one catch-all route when at least one action is registered.
- dispatch(request: HttpRequest, uid: str) HttpResponse[source]#
Forward a POST request to FormActionDispatch.dispatch.
- get_meta(action_name: str, page_path: str | None = None) ActionMeta | None[source]#
Return stored ActionMeta for the name, if any.
- iter_actions() Iterable[ActionMeta][source]#
Yield every stored ActionMeta in registration order.
- next.forms.backends.build_action_guard(*, login_required: bool = False, permission_required: str | Iterable[str] | None = None) ActionGuard | None[source]#
Return an ActionGuard for the declared requirements, or None when unset.
- next.forms.backends.file_to_dotted_module(file_path: str) str[source]#
Return a dotted module name by walking up while __init__.py exists.
Rendering#
render_form_page_with_errors re-renders the origin page template with a bound form in context.
It is the body of FormActionBackend.render_invalid_page in the bundled backend and imports from next.forms.rendering directly.
The rendered HTML flows through the static-assets pipeline, so co-located CSS and JS land in the response.
HTML rendering for validation-error responses.
- next.forms.rendering.render_form_page_with_errors(backend: FormActionBackend, request: HttpRequest, params: _ErrorRenderParams, page_file_path: Path) str[source]#
Render the page template for page_file_path with a bound form in context.
The rendered HTML flows through Page.render_with_static_assets so co-located CSS and JS land in the response and any request-aware backend (such as a per-tenant URL prefix) sees the same request it does on the canonical render path.
Registration diagnostics#
RegistrationDiagnostics buffers registration problems for the forms system checks, exposed as the module-level registration_diagnostics instance.
The registration paths write into it and next.forms.checks reads it when manage.py check runs.
Both import from next.forms.diagnostics directly.
The test isolation helper next.testing.reset_form_registration_state() clears the buffers between cases.
Diagnostics buffers accumulated during form registration.
- class next.forms.diagnostics.RegistrationDiagnostics(outside_base_dir: list[tuple[str, str]]=<factory>, invalid_meta_scope: list[tuple[str, str]]=<factory>, invalid_action_scope: list[tuple[str, str]]=<factory>, instance_from_url_unknown_field: list[tuple[str, str, str]]=<factory>, instance_from_url_on_non_model_form: list[str] = <factory>, action_collisions: dict[str, set[tuple[str, str]]]=<factory>, shared_name_collisions: dict[str, set[str]]=<factory>, action_applied_to_class: list[str] = <factory>, wizard_without_steps: list[str] = <factory>)[source]#
Registration problems collected for the forms system checks.
- snapshot() RegistrationDiagnostics[source]#
Return an independent deep copy of every buffer.
- restore(snapshot: RegistrationDiagnostics) None[source]#
Replace the buffer contents in place with deep copies of a snapshot.
- __init__(outside_base_dir: list[tuple[str, str]]=<factory>, invalid_meta_scope: list[tuple[str, str]]=<factory>, invalid_action_scope: list[tuple[str, str]]=<factory>, instance_from_url_unknown_field: list[tuple[str, str, str]]=<factory>, instance_from_url_on_non_model_form: list[str] = <factory>, action_collisions: dict[str, set[tuple[str, str]]]=<factory>, shared_name_collisions: dict[str, set[str]]=<factory>, action_applied_to_class: list[str] = <factory>, wizard_without_steps: list[str] = <factory>) None#
Action URL helpers#
reverse_form_action resolves the dispatch URL for an action UID under either URL wiring,
the namespaced next:form_action route or the bare form_action route.
It lives in next.forms.uid and is not re-exported at the package level.
ORIGIN_FIELD_NAME is the wire name of the hidden origin field every rendered form carries, "_next_form_origin".
validated_origin_path accepts a posted origin value only as a same-site path.
redirect_to_origin builds the success redirect back to the page named by the posted origin field, falling back to fallback when the field is absent or off-site.
It is re-exported from next.forms.
FORM_ORIGIN_OVERRIDE_KEY names the render-context key whose value overrides the origin of a rendered form, which the partial shaping layer sets to the next step URL on a wizard advance.
Dispatch-URL reversing, origin-path validation, and origin redirects.
- next.forms.uid.redirect_to_origin(request: HttpRequest, fallback: str = '/') HttpResponseRedirect[source]#
Redirect back to the page that rendered the form.
Origin resolution#
OriginMatch, resolve_origin, resolve_url_to_match, and resolve_url_to_page are re-exported from next.forms.
resolve_origin resolves the posted _next_form_origin field into an OriginMatch and memoises the result on the request, so the dispatcher and every {% form %} tag on a re-rendered page share one resolution.
resolve_url_to_match resolves any same-site URL against the URLconf, and passing filter_reserved=False keeps the captured URL kwargs raw instead of dropping the names the dependency resolver reserves.
resolve_url_to_page returns only the page path of the resolved view, or None when the URL does not name a routed page.
Server-side resolution of the posted form origin to the page it names.
- class next.forms.origin.OriginMatch(page_path: Path | None, url_kwargs: dict[str, object], origin: str)[source]#
Resolved identity of the page named by a same-site origin URL.
- next.forms.origin.resolve_origin(request: HttpRequest) OriginMatch | None[source]#
Return the posted-origin match for the request, memoised on the request.
- next.forms.origin.resolve_url_to_match(url: str, request: HttpRequest, *, filter_reserved: bool = True) OriginMatch | None[source]#
Resolve a same-site URL against the URLconf to a page identity.
The URL travels through the same URLconf the request uses, with the script prefix stripped. Set filter_reserved to keep the captured URL kwargs raw when the caller needs every captured parameter rather than only the DI-safe ones.
Formset helpers#
The Django factories formset_factory, modelformset_factory, and inlineformset_factory re-export through next.forms unchanged.
cleanup_extra_initial is the framework addition, and the module forwards every other public django.forms.formsets name at runtime.
Helpers for working with Django formsets in custom UIs.
Frozen specs#
Frozen-dataclass specs for rendering Django forms in custom templates.
- class next.forms.serializers.FieldSpec(bound: BoundField, kind: FieldKind, input_type: str, value: Any, selected: tuple[str, ...], is_extra: bool)[source]#
Render-time descriptor for one BoundField.
- bound: BoundField#
- kind: FieldKind#
- value: Any#
- class next.forms.serializers.FormSectionSpec(label: str, description: str, fields: tuple[FieldSpec, ...])[source]#
One labelled section in a FormSpec (matches a Django admin fieldset).
- class next.forms.serializers.FormSpec(sections: tuple[FormSectionSpec, ...], non_field_errors: tuple[str, ...])[source]#
Top-level spec for rendering a form with optional fieldsets.
- sections: tuple[FormSectionSpec, ...]#
- class next.forms.serializers.FormsetRowSpec(fields: tuple[FieldSpec, ...], hidden_html: str, delete_field: BoundField | None, errors: Mapping[str, list[str]], is_extra: bool)[source]#
One row inside a FormsetSpec. Render hidden_html with |safe.
- class next.forms.serializers.FormsetSpec(prefix: str, verbose_name_plural: str, management_form: BaseForm, rows: tuple[FormsetRowSpec, ...], non_form_errors: tuple[str, ...], can_delete: bool)[source]#
Template-friendly view of a Django formset (inline or standalone).
- rows: tuple[FormsetRowSpec, ...]#
- next.forms.serializers.field_spec(bound: BoundField, *, is_extra: bool = False) FieldSpec[source]#
Classify a BoundField into a FieldSpec.
- next.forms.serializers.form_spec(form: BaseForm, fieldsets: Sequence[tuple[str | None, Mapping[str, Any]]] | None = None) FormSpec[source]#
Group form’s fields into sections per Django admin (label, opts).
- next.forms.serializers.formset_spec(formset: BaseFormSet) FormsetSpec[source]#
Build a FormsetSpec from a Django formset.
Signals#
See Signals reference and Form signals for the form signals
(action_registered, action_dispatched, form_validation_failed,
wizard_step_submitted, wizard_completed, form_access_denied).
See also#
See also
Forms for the topic subtree. Extending for plugging custom backends. Testing for helpers used when asserting handlers. Action dispatch for the dispatch pipeline.