{"record":{"id":"57f9557c135ccc3b","repo":"odoo/odoo","slug":"there-is-a-syntax-error-in-the-barcode-pattern-p","errorCode":null,"errorMessage":"There is a syntax error in the barcode pattern %(pattern)s: braces can only contain N's followed by D's.","messagePattern":"There is a syntax error in the barcode pattern (.+?): braces can only contain N's followed by D's\\.","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"error","filePath":"addons/barcodes/models/barcode_rule.py","lineNumber":37,"sourceCode":"            ('ean8', 'EAN-8'),\n            ('upca', 'UPC-A'),\n        ], help='This rule will apply only if the barcode is encoded with the specified encoding')\n    type = fields.Selection(\n        string='Type', required=True, selection=[\n            ('alias', 'Alias'),\n            ('product', 'Unit Product'),\n        ], default='product')\n    pattern = fields.Char(string='Barcode Pattern', help=\"The barcode matching pattern\", required=True, default='.*')\n    alias = fields.Char(string='Alias', default='0', help='The matched pattern will alias to this barcode', required=True)\n\n    @api.constrains('pattern')\n    def _check_pattern(self):\n        for rule in self:\n            p = rule.pattern.replace('\\\\\\\\', 'X').replace('\\\\{', 'X').replace('\\\\}', 'X')\n            findall = re.findall(\"[{]|[}]\", p)  # p does not contain escaped { or }\n            if len(findall) == 2:\n                if not re.search(\"[{][N]*[D]*[}]\", p):\n                    raise ValidationError(_(\"There is a syntax error in the barcode pattern %(pattern)s: braces can only contain N's followed by D's.\", pattern=rule.pattern))\n                elif re.search(\"[{][}]\", p):\n                    raise ValidationError(_(\"There is a syntax error in the barcode pattern %(pattern)s: empty braces.\", pattern=rule.pattern))\n            elif len(findall) != 0:\n                raise ValidationError(_(\"There is a syntax error in the barcode pattern %(pattern)s: a rule can only contain one pair of braces.\", pattern=rule.pattern))\n            elif p == '*':\n                raise ValidationError(_(\" '*' is not a valid Regex Barcode Pattern. Did you mean '.*'?\"))\n            try:\n                re.compile(re.sub('{N+D*}', '', p))\n            except re.error:\n                raise ValidationError(_(\"The barcode pattern %(pattern)s does not lead to a valid regular expression.\", pattern=rule.pattern))\n","sourceCodeStart":19,"sourceCodeEnd":48,"githubUrl":"https://github.com/odoo/odoo/blob/1e661df964b1b264c9cef3ab28430d4785be3fda/addons/barcodes/models/barcode_rule.py#L19-L48","documentation":"Raised by the @api.constrains('pattern') check on barcode.rule (addons/barcodes/models/barcode_rule.py:37). Barcode patterns may contain at most one brace pair, and inside the braces only the letters N (any digit) followed by D (decimal digits) are allowed, e.g. {NNNDD}. After stripping escaped braces (\\{, \\}, \\\\), the code regex-searches the remaining text for [{][N]*[D]*[}]; if exactly two brace characters exist but no {N*D*} match is found, this ValidationError is raised on record save/create/write.","triggerScenarios":"Creating or writing a barcode.rule record whose pattern field contains a brace pair whose content is not zero-or-more N's followed by zero-or-more D's — e.g. '12{NNX}', '{DNN}' (D before N), '{nn}' (lowercase), or '{N-D}'. Only fires when exactly 2 brace chars remain after replacing '\\\\', '\\{', '\\}' with 'X'.","commonSituations":"Admins hand-editing nomenclature rules in Settings > Technical > Barcode Nomenclatures; importing rule data via CSV/XML with malformed patterns; forgetting that the placeholder vocabulary is only N and D.","solutions":["Fix the pattern so braces contain only N's followed by D's, e.g. '{NNDD}' or '{NNN}'.","If a literal '{' or '}' is intended in the regex, escape it as '\\{' / '\\}' so it is ignored by this check.","If the brace content is not a numeric placeholder, remove the braces entirely and use plain regex digit classes like '[0-9]{4}'."],"exampleFix":"// before\nrule.pattern = '^{NNX}'            // X inside braces -> ValidationError\n// after\nrule.pattern = '^{NND}'             // N's then D's only","handlingStrategy":"validation","validationCode":"import re\n\ndef valid_brace_content(pattern: str) -> bool:\n    p = pattern.replace('\\\\\\\\', 'X').replace('\\\\{', 'X').replace('\\\\}', 'X')\n    braces = re.findall('[{]|[}]', p)\n    return len(braces) != 2 or bool(re.search('[{][N]+[D]*[}]|[{][N]*[D]+[}]', p))","typeGuard":null,"tryCatchPattern":"from odoo.exceptions import ValidationError\ntry:\n    rule.write({'pattern': new_pattern})\nexcept ValidationError as e:\n    # surface to user; keep previous pattern\n    raise UserError(str(e)) from e","preventionTips":["Author patterns with the N/D placeholder vocabulary only; never regex quantifier braces.","Test candidate patterns in a Python shell with the same checks before saving records.","Validate pattern data in import scripts before create/write."],"tags":["odoo","barcode","regex","validation","constraint"],"backgroundTag":null,"analyzedSha":"1e661df964b1b264c9cef3ab28430d4785be3fda","analyzedAt":"2026-08-15T05:22:16.142Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}