{"record":{"id":"f99aace557cb65a4","repo":"dgtlmoon/changedetection.io","slug":"processor-processor-name-does-not-support-xlsx","errorCode":null,"errorMessage":"Processor '{processor_name}' does not support xlsx export","messagePattern":"Processor '(.+?)' does not support xlsx export","errorType":"http","errorClass":null,"httpStatus":404,"severity":"error","filePath":"changedetectionio/blueprint/ui/diff.py","lineNumber":395,"sourceCode":"            uuid = list(datastore.data['watching'].keys()).pop()\n        try:\n            watch = datastore.data['watching'][uuid]\n        except KeyError:\n            flash(gettext(\"No history found for the specified link, bad link?\"), \"error\")\n            return redirect(url_for('watchlist.index'))\n\n        processor_name = watch.get('processor', 'text_json_diff')\n        from changedetectionio.processors import get_processor_submodule\n        processor_module = get_processor_submodule(processor_name, 'difference')\n\n        if processor_module and hasattr(processor_module, 'export_xlsx'):\n            data, filename = processor_module.export_xlsx(watch=watch, datastore=datastore)\n            resp = make_response(data)\n            resp.headers['Content-Type'] = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'\n            resp.headers['Content-Disposition'] = f'attachment; filename=\"{filename}\"'\n            return resp\n\n        abort(404, description=f\"Processor '{processor_name}' does not support xlsx export\")\n\n    @diff_blueprint.route(\"/diff/<uuid_str:uuid>/extract\", methods=['GET'])\n    @login_optionally_required\n    def diff_history_page_extract_GET(uuid):\n        \"\"\"\n        Render the data extraction form for a watch.\n\n        This route is processor-aware: it delegates to the processor's\n        extract.py module, allowing different processor types to provide\n        custom extraction interfaces.\n\n        Each processor implements processors/{type}/extract.py::render_form()\n        If a processor doesn't have an extract module, falls back to text_json_diff.\n        \"\"\"\n\n\n        if uuid == 'first':\n            uuid = list(datastore.data['watching'].keys()).pop()","sourceCodeStart":377,"sourceCodeEnd":413,"githubUrl":"https://github.com/dgtlmoon/changedetection.io/blob/5d9c7c6da76340597243e8163c4f2439237fa0e8/changedetectionio/blueprint/ui/diff.py#L377-L413","documentation":"abort(404) in the xlsx export endpoint when the watch's processor does not implement export_xlsx(). Only processors that provide a spreadsheet export (e.g. restock price/stock timelines) support /diff/<uuid>/processor-export.xlsx; others fall through to this 404.","triggerScenarios":"GET /diff/<uuid>/processor-export.xlsx for a watch whose processor module lacks an export_xlsx(watch, datastore) function (the hasattr check fails), or the processor submodule lookup returned None.","commonSituations":"Trying to export history from the default 'text' watch/processor which has no xlsx exporter; custom processors that never implemented export_xlsx; expecting parity with processors that do support export.","solutions":["Confirm the watch's processor type supports xlsx export (e.g. restock processors)","Switch the watch to a processor that implements export_xlsx before requesting the export","If writing a custom processor, implement export_xlsx(watch, datastore) returning (data, filename)"],"exampleFix":"# before\nresp = requests.get(f'{base}/diff/{uuid}/processor-export.xlsx')\nresp.raise_for_status()\n\n# after\nresp = requests.get(f'{base}/diff/{uuid}/processor-export.xlsx')\nif resp.status_code == 404:\n    raise RuntimeError('processor has no xlsx export; use a restock processor')\nresp.raise_for_status()","handlingStrategy":"validation","validationCode":"from changedetectionio.processors import get_processor_submodule\ndef supports_xlsx(name):\n    m = get_processor_submodule(name, 'difference')\n    return m is not None and hasattr(m, 'export_xlsx')","typeGuard":"def can_export_xlsx(processor_module) -> bool:\n    return hasattr(processor_module, 'export_xlsx') and callable(processor_module.export_xlsx)","tryCatchPattern":"try:\n    data, filename = processor_module.export_xlsx(watch=watch, datastore=datastore)\nexcept (AttributeError, TypeError):\n    abort(404, description=\"Processor does not support xlsx export\")","preventionTips":["Gate export buttons on processor capability checks","Implement export_xlsx in custom processors or disable the UI affordance","Test exports for each processor type after upgrades"],"tags":["flask","export","xlsx","processor-plugin","http-404"],"backgroundTag":"export-format-unsupported","analyzedSha":"5d9c7c6da76340597243e8163c4f2439237fa0e8","analyzedAt":"2026-08-27T19:41:16.067Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}