{"record":{"id":"353a6ca0416f8f36","repo":"django/django","slug":"band-indices-are-not-allowed-for-this-operator-it","errorCode":null,"errorMessage":"Band indices are not allowed for this operator, it works on bbox only.","messagePattern":"Band indices are not allowed for this operator, it works on bbox only\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"django/contrib/gis/db/backends/postgis/operations.py","lineNumber":58,"sourceCode":"    def as_sql(self, connection, lookup, template_params, *args):\n        template_params = self.check_raster(lookup, template_params)\n        template_params = self.check_geography(lookup, template_params)\n        return super().as_sql(connection, lookup, template_params, *args)\n\n    def check_raster(self, lookup, template_params):\n        spheroid = lookup.rhs_params and lookup.rhs_params[-1] == \"spheroid\"\n\n        # Check which input is a raster.\n        lhs_is_raster = lookup.lhs.field.geom_type == \"RASTER\"\n        rhs_is_raster = isinstance(lookup.rhs, GDALRaster)\n\n        # Look for band indices and inject them if provided.\n        if lookup.band_lhs is not None and lhs_is_raster:\n            if not isinstance(lookup.band_lhs, int):\n                name = lookup.band_lhs.__class__.__name__\n                raise TypeError(f\"Band index must be an integer, but got {name!r}.\")\n            if not self.func:\n                raise ValueError(\n                    \"Band indices are not allowed for this operator, it works on bbox \"\n                    \"only.\"\n                )\n            template_params[\"lhs\"] = \"%s, %s\" % (\n                template_params[\"lhs\"],\n                lookup.band_lhs,\n            )\n\n        if lookup.band_rhs is not None and rhs_is_raster:\n            if not isinstance(lookup.band_rhs, int):\n                name = lookup.band_rhs.__class__.__name__\n                raise TypeError(f\"Band index must be an integer, but got {name!r}.\")\n            if not self.func:\n                raise ValueError(\n                    \"Band indices are not allowed for this operator, it works on bbox \"\n                    \"only.\"\n                )\n            template_params[\"rhs\"] = \"%s, %s\" % (","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/django/django/blob/ae25a40be07e8a749edf526df37c93e59d4a22c9/django/contrib/gis/db/backends/postgis/operations.py#L40-L76","documentation":"ValueError raised in PostGISOperator.check_raster() (line 57-61) when a left-hand-side band index is provided for a raster operator that has no func attribute — i.e., a bounding-box-only operator defined purely by an SQL op symbol (like ~, &&, @). These operators work on the raster's convex hull and cannot reference individual bands.","triggerScenarios":"Using a bbox operator with a band index: MyRasterModel.objects.filter(rast__bbcontains=(other_raster, 1)) or rast__bboverlaps=(other, 1), where the operator (bbcontains, bboverlaps, contained, overlaps_left, etc.) has no func and the tuple includes a band index on the LHS.","commonSituations":"Assuming all raster operators accept band indices; copy-pasting a band index from a contains/intersects lookup into a bboverlaps lookup.","solutions":["Remove the band index from bbox-only lookups (bbcontains, bboverlaps, contained, left, right, strictly_above, etc.).","If band-level comparison is needed, switch to a func-based operator like contains, intersects, or within that supports band indices.","Consult gis_operators in PostGISOperations to confirm which operators accept raster band indices."],"exampleFix":"// before — bbox operator with band index\nMyRasterModel.objects.filter(rast__bboverlaps=(other_rast, 1))\n// ValueError: Band indices are not allowed for this operator, it works on bbox only.\n\n// after — drop the band index for bbox operators\nMyRasterModel.objects.filter(rast__bboverlaps=other_rast)\n// or use a func-based operator that supports bands:\nMyRasterModel.objects.filter(rast__contains=(other_rast, 1))","handlingStrategy":"validation","validationCode":"from django.contrib.gis.db.backends.postgis.operations import PostGISOperations\n\nBBOX_ONLY_OPS = {\n    name for name, op in PostGISOperations.gis_operators.items()\n    if not getattr(op, 'func', None)\n}\n\ndef supports_band_index(lookup_name: str) -> bool:\n    return lookup_name not in BBOX_ONLY_OPS","typeGuard":"def is_bbox_only_operator(lookup_name: str) -> bool:\n    op = PostGISOperations.gis_operators.get(lookup_name)\n    return op is not None and not getattr(op, 'func', None)","tryCatchPattern":"try:\n    Model.objects.filter(rast__bboverlaps=(other, 1))\nexcept ValueError:\n    # bbox operator — drop the band index\n    Model.objects.filter(rast__bboverlaps=other)","preventionTips":["Never pass band indices to bbox-only operators (bbcontains, bboverlaps, contained, left, right, etc.).","Only func-based operators (contains, intersects, within, covers, dwithin) accept band indices.","Centralize raster lookup construction in a helper that validates band-index usage per operator."],"tags":["gis","postgis","raster","bbox","band-index","geodjango"],"analyzedSha":"ae25a40be07e8a749edf526df37c93e59d4a22c9","analyzedAt":"2026-08-06T21:46:51.801Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}