Source code for autowisp.browser_interface.diagnostics.forms

"""The form behind the diagnostic expression management page.

The library lives in the project database rather than in a Django model,
so this is a plain ``Form``: Django still does the per-field checks and
error plumbing, while the one check a model used to supply -- that the name
is not already taken -- is made here against the library.

What Django cannot know is whether the expression *means* anything.  That
is :func:`~autowisp.diagnostics.expressions.check_expression`, which
returns its complaints as plain strings rather than raising so that it
stays usable with no Django at all.  Turning those strings into
``ValidationError``\\ s is this module's whole reason to exist, and the only
place that adaptation happens.
"""

from django import forms

from autowisp.diagnostics.expressions import check_expression


[docs] class DiagnosticExpressionForm(forms.Form): """ Validate one proposed expression against the library it would join. Validity does not depend on what the project has recorded -- see :mod:`autowisp.diagnostics.diagnostic_types` -- so only the library is consulted; whether the diagnostics an expression needs exist is a separate question, answered by counting rows elsewhere. """ # Selected through ``image/<slug:x>/vs/<slug:y>``, so a name outside the # slug charset could be stored but never plotted. name = forms.SlugField( max_length=100, help_text="Name shown in the diagnostics selectors", ) # Neither has a useful length limit, but both are written on one line, # so a textarea would be a misleading amount of room. expression = forms.CharField( widget=forms.TextInput(), help_text="Python expression over per-image diagnostic names", ) description = forms.CharField( required=False, widget=forms.TextInput(), help_text="What the expression is for", )
[docs] def __init__(self, *args, expressions=None, replacing=None, **kwargs): """ Args: expressions(dict): The library, ``{name: expression}``, this one would join. The view has it and the form does not, so it arrives as a keyword argument. replacing(str or None): The name of the stored expression being edited, or ``None`` when adding. An entry of that name is treated as the one being replaced rather than as a conflict, so an edit can pass the library unchanged. """ super().__init__(*args, **kwargs) self.expressions = dict(expressions or {}) self.replacing = replacing or None
[docs] def clean(self): """Report what ``check_expression`` says, against the field at fault.""" cleaned_data = super().clean() name = cleaned_data.get("name") expression = cleaned_data.get("expression") if not name or not expression: # A missing field has already failed; complaining about the # pair as well would only repeat that. return cleaned_data if name != self.replacing and name in self.expressions: self.add_error("name", f"An expression named {name} exists.") return cleaned_data # check_expression reports on the pair, but its complaints have to # land on the field that caused them. The problems a name has on # its own are exactly those it still has beside an expression that # references nothing, which sorts them without matching on the # message text. name_problems = check_expression(name, "0", {}) for problem in check_expression(name, expression, self.expressions): self.add_error( "name" if problem in name_problems else "expression", problem ) return cleaned_data