django/django · error · ValueError

Band indices are not allowed for this operator, it works on…

Error message

Band indices are not allowed for this operator, it works on bbox only.

What it means

This ValueError is raised by PostGISOperator.check_raster() (django/contrib/gis/db/backends/postgis/operations.py:57-61) when a band index is provided for the left-hand side of a raster lookup but the operator has no SQL function (self.func is falsy). Operators without a func attribute work purely on bounding boxes (e.g., the &&, ~, @ operators for bboverlaps, bbcontains, contained) and PostGIS does not support per-band operations on bbox-only operators. The error fires when lhs_is_raster is True, band_lhs is an integer, but self.func is not set.

Solutions

  1. Remove the band index from bbox-only operators: use filter(rast__bbcontains=other_raster) instead of filter(rast__bbcontains=(other_raster, 1)).
  2. Use a function-based operator (contains, intersects, within) if per-band comparison is needed, as those have a func and support band indices.
  3. Check the operator definition in gis_operators to determine if it uses a func (supports bands) or only an op (bbox-only).

Example fix

# before
qs = MyRasterModel.objects.filter(rast__bbcontains=(other_raster, 1))
# ValueError: Band indices are not allowed for this operator, it works on bbox only.

# after
# Remove band index for bbox operators
qs = MyRasterModel.objects.filter(rast__bbcontains=other_raster)
# Or use a function-based operator with band index
qs = MyRasterModel.objects.filter(rast__contains=(other_raster, 1))
Defensive patterns

Strategy: validation

Validate before calling

BBOX_ONLY_RASTER_OPERATORS = {'bbcontains', 'bboverlaps', 'contained',
                               'overlaps_left', 'overlaps_right', 'overlaps_below',
                               'overlaps_above', 'left', 'right', 'strictly_below',
                               'strictly_above', 'same_as', 'exact'}

def allows_band_index(operator_name):
    return operator_name not in BBOX_ONLY_RASTER_OPERATORS

# Use before adding band indices to a raster lookup:
# if allows_band_index('bbcontains'):
#     qs = Model.objects.filter(rast__bbcontains=(raster, band))  # would work
# else:
#     qs = Model.objects.filter(rast__bbcontains=raster)  # no band

Type guard

BBOX_ONLY_RASTER_OPERATORS = {'bbcontains', 'bboverlaps', 'contained',
    'overlaps_left', 'overlaps_right', 'overlaps_below', 'overlaps_above',
    'left', 'right', 'strictly_below', 'strictly_above', 'same_as', 'exact'}

def allows_band_index(operator_name):
    return operator_name not in BBOX_ONLY_RASTER_OPERATORS

Try / catch

try:
    qs = MyRasterModel.objects.filter(rast__bbcontains=(other_raster, 1))
    results = list(qs)
except ValueError as e:
    if 'Band indices are not allowed' in str(e):
        # Remove band index and retry
        qs = MyRasterModel.objects.filter(rast__bbcontains=other_raster)
        results = list(qs)
    else:
        raise

Prevention

When it happens

Trigger: Using a bounding-box raster operator (bbcontains, bboverlaps, contained) with a band index on a RasterField. For example: MyRasterModel.objects.filter(rast__bbcontains=(other_raster, 1)) — the bbcontains operator uses the MBR (~) operator which has no func, so band indices are meaningless. The band index parameter is rejected because bbox operators compare whole raster extents, not individual bands.

Common situations: Assuming all raster operators accept band indices when only function-based operators (ST_Contains, ST_Intersects, etc.) do. Copying a band-indexed lookup pattern from a function-based operator to a bbox operator. Programmatically applying band indices to all raster lookups without distinguishing operator types.

Related errors


AI-assisted analysis of django/django@b5388a3a80 (2026-08-10). Data as JSON: /api/errors/353a6ca0416f8f36. Report an issue: GitHub.

Appendix: source

Thrown at django/contrib/gis/db/backends/postgis/operations.py:58

    def as_sql(self, connection, lookup, template_params, *args):
        template_params = self.check_raster(lookup, template_params)
        template_params = self.check_geography(lookup, template_params)
        return super().as_sql(connection, lookup, template_params, *args)

    def check_raster(self, lookup, template_params):
        spheroid = lookup.rhs_params and lookup.rhs_params[-1] == "spheroid"

        # Check which input is a raster.
        lhs_is_raster = lookup.lhs.field.geom_type == "RASTER"
        rhs_is_raster = isinstance(lookup.rhs, GDALRaster)

        # Look for band indices and inject them if provided.
        if lookup.band_lhs is not None and lhs_is_raster:
            if not isinstance(lookup.band_lhs, int):
                name = lookup.band_lhs.__class__.__name__
                raise TypeError(f"Band index must be an integer, but got {name!r}.")
            if not self.func:
                raise ValueError(
                    "Band indices are not allowed for this operator, it works on bbox "
                    "only."
                )
            template_params["lhs"] = "%s, %s" % (
                template_params["lhs"],
                lookup.band_lhs,
            )

        if lookup.band_rhs is not None and rhs_is_raster:
            if not isinstance(lookup.band_rhs, int):
                name = lookup.band_rhs.__class__.__name__
                raise TypeError(f"Band index must be an integer, but got {name!r}.")
            if not self.func:
                raise ValueError(
                    "Band indices are not allowed for this operator, it works on bbox "
                    "only."
                )
            template_params["rhs"] = "%s, %s" % (

View on GitHub (pinned to b5388a3a80)