{"record":{"id":"baf4d28a768dfa83","repo":"dgtlmoon/changedetection.io","slug":"empty-value-not-allowed","errorCode":null,"errorMessage":"Empty value not allowed.","messagePattern":"Empty value not allowed\\.","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"warning","filePath":"changedetectionio/forms.py","lineNumber":816,"sourceCode":"    def __call__(self, form, field):\n        data = field.data\n        if not data:\n            return\n\n        # normalize into list of lines\n        if isinstance(data, str) and self.split_lines:\n            lines = data.splitlines()\n        elif isinstance(data, (list, tuple)):\n            lines = data\n        else:\n            lines = [data]\n\n        for line in lines:\n            stripped = line.strip()\n            if not stripped:\n                if self.allow_empty:\n                    continue\n                raise ValidationError(self.message or _l(\"Empty value not allowed.\"))\n            if not self.pattern.match(stripped):\n                raise ValidationError(self.message or _l(\"Invalid value.\"))\n\ndef visual_browser_choices():\n    \"\"\"Browsers that can render the Add-Watch live preview, as RadioField choices.\n\n    Lazy import (the add_watch_ui blueprint imports this module) and empty outside an\n    app context, because WTForms evaluates a choices callable on field construction.\n    \"\"\"\n    from flask import current_app, has_app_context\n    from changedetectionio.blueprint.add_watch_ui import browser_config\n\n    if not has_app_context():\n        return []\n    datastore = current_app.config.get('DATASTORE')\n    return browser_config.radio_choices(datastore) if datastore else []\n\n","sourceCodeStart":798,"sourceCodeEnd":834,"githubUrl":"https://github.com/dgtlmoon/changedetection.io/blob/5d9c7c6da76340597243e8163c4f2439237fa0e8/changedetectionio/forms.py#L798-L834","documentation":"A MultiLineField-style WTForms validator that splits input into lines raises ValidationError when a line is empty (after strip) and allow_empty is False. It means the submitted multi-line value contained a blank line where the field configuration forbids empty entries.","triggerScenarios":"Submitting a form field validated by this class (e.g. a list of URLs/tokens, one per line) that contains a blank line, when the validator was constructed with allow_empty=False (or default). Trailing blank line with trailing whitespace still triggers it because each line is stripped first.","commonSituations":"Copy-pasting URL lists from spreadsheets or text files that include empty rows; a textarea auto-appending a newline; users pressing Enter twice between entries.","solutions":["Remove blank lines from the textarea input","If blank lines should be tolerated, construct the validator with allow_empty=True","Pre-clean input client-side by filtering out lines that are empty after strip"],"exampleFix":"# before\nvalidator = MultiLineRegexValidator(pattern=..., allow_empty=False)\n# after\nvalidator = MultiLineRegexValidator(pattern=..., allow_empty=True)","handlingStrategy":"validation","validationCode":"value = form.textarea.data\nlines = [l for l in value.splitlines() if l.strip()]\ncleaned = '\\n'.join(lines)  # submit this instead","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Filter empty lines from multi-line textareas before submission","Choose allow_empty=True when blank lines are acceptable input"],"tags":["wtforms","validation","form-input","multi-line"],"backgroundTag":"form-validation-failed","analyzedSha":"5d9c7c6da76340597243e8163c4f2439237fa0e8","analyzedAt":"2026-08-27T19:41:16.067Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}