import contextlib
import arg
from django import forms
from django.core.exceptions import ValidationError
@contextlib.contextmanager
def _only_raise_validation_error():
"""
Re-raise everything as a Django ValidationError so that form cleaning
works seamlessly.
"""
try:
yield
except Exception as exc:
if isinstance(exc, ValidationError):
raise
else:
raise ValidationError(exc) from exc
[docs]def get_field_validator(func, field_label):
"""
Given a field label and function, generate a form field validator
from the function. It is assumed that the function is wrapped with
``python-args``.
Django form fields need to raise ValidationErrors in order for
errors to bubble up properly. This function wraps any
validators for the field and re-raises ValidationErrors
"""
def validate_field(val):
with _only_raise_validation_error():
func.partial.pre_func(**{field_label: val})
return validate_field
[docs]def adapt(form, func, default_args=None, clean=True):
"""
Adapt a form to an python-args func, ensuring the form validation behaves
seamlessly with function validation.
Evaluate any djarg.form.Field classes that are fields of the form.
Args:
form (django.forms.Form): The Django form being adapted.
func (function): A function decorated with ``python-args`` decorators.
default_args (dict, default=None): A dictionary of any other default
arguments that are used when calling the ``python-args`` function.
clean (bool, default=True): Adapt the clean method to the validators
of the func
"""
default_args = default_args or {}
# Instantiate any lazy fields
for label, field in form.fields.items():
if isinstance(field, arg.Lazy):
form.fields[label] = arg.load(
field, **{**{'form': form}, **default_args}
)
if clean: # pragma: no branch
# Attached additional form field validators
for label, field in form.fields.items():
field.validators.append(get_field_validator(func, label))
# Attach additional form clean methods
form.clean = get_form_clean(func, form, default_args=default_args)
return form
[docs]class Field(arg.init, forms.Field):
"""A lazy ``python-args`` adapter to lazily load a Django form field.
When declaring a form, a form field can be wrapped in the ``Field``
object so that all attributes can be lazily loaded.
Example:
This is an example of lazily loading a form field so that we
can dynamically fill out help text::
class MyForm(forms.Form):
field = Field(CharField, help_text=args.func(get_help_text))
"""
def __init__(self, *args, **kwargs):
arg.init.__init__(self, *args, **kwargs)
forms.Field.__init__(self)
def __getattribute__(self, name):
return forms.Field.__getattribute__(self, name)