a class that represents range boundaries def initself inclusivelowertrue inclusiveupperfalse self lower if inclusivelower else self upper if inclusiveupper else def assqlself compiler connection return ss self lower self upper class rangeoperators https www postgresql orgdocscurrentfunctionsrange htmlrangeoperatorstable equal notequal contains containedby overlaps fullylt fullygt notlt notgt adjacentto class rangefieldmodels field emptystringsallowed false def initself args kwargs if defaultbounds in kwargs raise typeerror fcannot use defaultbounds with self class name initializing basefield here ensures that its model matches the model for self if hasattrself basefield self basefield self basefield super initargs kwargs property def modelself try return self dictmodel except keyerror raise attributeerror s object has no attribute model self class name model setter def modelself model self dictmodel model self basefield model model classmethod def choicesisvaluecls value return isinstancevalue list tuple or super choicesisvaluevalue def getplaceholderself value compiler connection return s formatself dbtypeconnection def getprepvalueself value if value is none return none elif isinstancevalue range return value elif isinstancevalue list tuple return self rangetypevalue0 value1 return value def topythonself value if isinstancevalue str assume we re deserializing vals json loadsvalue for end in lower upper if end in vals valsend self basefield topythonvalsend value self rangetypevals elif isinstancevalue list tuple value self rangetypevalue0 value1 return value def setattributesfromnameself name super setattributesfromnamename self basefield setattributesfromnamename def valuetostringself obj value self valuefromobjectobj if value is none return none if value isempty return json dumpsempty true basefield self basefield result bounds value bounds for end in lower upper val getattrvalue end if val is none resultend none else obj attributesetterbasefield attname val resultend basefield valuetostringobj return json dumpsresult def formfieldself kwargs kwargs setdefaultformclass self formfield return super formfieldkwargs canonicalrangebounds class continuousrangefieldrangefield def initself args defaultboundscanonicalrangebounds kwargs if defaultbounds not in raise valueerrordefaultbounds must be one of or self defaultbounds defaultbounds super initargs kwargs def getprepvalueself value if isinstancevalue list tuple return self rangetypevalue0 value1 self defaultbounds return super getprepvaluevalue def formfieldself kwargs kwargs setdefaultdefaultbounds self defaultbounds return super formfieldkwargs def deconstructself name path args kwargs super deconstruct if self defaultbounds and self defaultbounds canonicalrangebounds kwargsdefaultbounds self defaultbounds return name path args kwargs class integerrangefieldrangefield basefield models integerfield rangetype numericrange formfield forms integerrangefield def dbtypeself connection return int4range class bigintegerrangefieldrangefield basefield models bigintegerfield rangetype numericrange formfield forms integerrangefield def dbtypeself connection return int8range class decimalrangefieldcontinuousrangefield basefield models decimalfield rangetype numericrange formfield forms decimalrangefield def dbtypeself connection return numrange class datetimerangefieldcontinuousrangefield basefield models datetimefield rangetype datetimetzrange formfield forms datetimerangefield def dbtypeself connection return tstzrange class daterangefieldrangefield basefield models datefield rangetype daterange formfield forms daterangefield def dbtypeself connection return daterange class rangecontainslookups datacontains def getpreplookupself if not isinstanceself rhs list tuple range return castself rhs self lhs field basefield return super getpreplookup rangefield registerlookuprangecontains rangefield registerlookuplookups containedby rangefield registerlookuplookups overlap class datetimerangecontainspostgresoperatorlookup lookupname contains postgresoperator rangeoperators contains def processrhsself compiler connection transform rhs value for db lookup if isinstanceself rhs datetime date value models valueself rhs self rhs value resolveexpressioncompiler query return super processrhscompiler connection def aspostgresqlself compiler connection sql params super aspostgresqlcompiler connection cast the rhs if needed castsql if isinstanceself rhs models expression and self rhs outputfieldornone and skip cast if rhs has a matching range type not isinstance self rhs outputfieldornone self lhs outputfield class castinternaltype self lhs outputfield basefield getinternaltype castsql formatconnection datatypes getcastinternaltype return ss sql castsql params daterangefield registerlookupdatetimerangecontains datetimerangefield registerlookupdatetimerangecontains class rangecontainedbypostgresoperatorlookup lookupname containedby typemapping smallint int4range integer int4range bigint int8range double precision numrange numeric numrange date daterange timestamp with time zone tstzrange postgresoperator rangeoperators containedby def processrhsself compiler connection rhs rhsparams super processrhscompiler connection ignore precision for decimalfields dbtype self lhs outputfield castdbtypeconnection split0 casttype self typemappingdbtype return s s rhs casttype rhsparams def processlhsself compiler connection lhs lhsparams super processlhscompiler connection if isinstanceself lhs outputfield models floatfield lhs s numeric lhs elif isinstanceself lhs outputfield models smallintegerfield lhs s integer lhs return lhs lhsparams def getpreplookupself return rangefield getprepvalueself rhs models datefield registerlookuprangecontainedby models datetimefield registerlookuprangecontainedby models integerfield registerlookuprangecontainedby models floatfield registerlookuprangecontainedby models decimalfield registerlookuprangecontainedby rangefield registerlookup class fullylessthanpostgresoperatorlookup lookupname fullylt postgresoperator rangeoperators fullylt rangefield registerlookup class fullgreaterthanpostgresoperatorlookup lookupname fullygt postgresoperator rangeoperators fullygt rangefield registerlookup class notlessthanpostgresoperatorlookup lookupname notlt postgresoperator rangeoperators notlt rangefield registerlookup class notgreaterthanpostgresoperatorlookup lookupname notgt postgresoperator rangeoperators notgt rangefield registerlookup class adjacenttolookuppostgresoperatorlookup lookupname adjacentto postgresoperator rangeoperators adjacentto rangefield registerlookup class rangestartswithmodels transform lookupname startswith function lower property def outputfieldself return self lhs outputfield basefield rangefield registerlookup class rangeendswithmodels transform lookupname endswith function upper property def outputfieldself return self lhs outputfield basefield rangefield registerlookup class isemptymodels transform lookupname isempty function isempty outputfield models booleanfield rangefield registerlookup class lowerinclusivemodels transform lookupname lowerinc function lowerinc outputfield models booleanfield rangefield registerlookup class lowerinfinitemodels transform lookupname lowerinf function lowerinf outputfield models booleanfield rangefield registerlookup class upperinclusivemodels transform lookupname upperinc function upperinc outputfield models booleanfield rangefield registerlookup class upperinfinitemodels transform lookupname upperinf function upperinf outputfield models booleanfield a class that represents range boundaries https www postgresql org docs current functions range html range operators table initializing base_field here ensures that its model matches the model for self assume we re deserializing continuous range field it allows specifying default bounds for list and tuple inputs lookup for date datetimerange containment to cast the rhs to the correct type transform rhs value for db lookup cast the rhs if needed skip cast if rhs has a matching range type ignore precision for decimalfields
import datetime
import json

from django.contrib.postgres import forms, lookups
from django.db import models
from django.db.backends.postgresql.psycopg_any import (
    DateRange,
    DateTimeTZRange,
    NumericRange,
    Range,
)
from django.db.models.functions import Cast
from django.db.models.lookups import PostgresOperatorLookup

from .utils import AttributeSetter

__all__ = [
    "RangeField",
    "IntegerRangeField",
    "BigIntegerRangeField",
    "DecimalRangeField",
    "DateTimeRangeField",
    "DateRangeField",
    "RangeBoundary",
    "RangeOperators",
]


class RangeBoundary(models.Expression):
    

    def __init__(self, inclusive_lower=True, inclusive_upper=False):
        self.lower = "[" if inclusive_lower else "("
        self.upper = "]" if inclusive_upper else ")"

    def as_sql(self, compiler, connection):
        return "'%s%s'" % (self.lower, self.upper), []


class RangeOperators:
    
    EQUAL = "="
    NOT_EQUAL = "<>"
    CONTAINS = "@>"
    CONTAINED_BY = "<@"
    OVERLAPS = "&&"
    FULLY_LT = "<<"
    FULLY_GT = ">>"
    NOT_LT = "&>"
    NOT_GT = "&<"
    ADJACENT_TO = "-|-"


class RangeField(models.Field):
    empty_strings_allowed = False

    def __init__(self, *args, **kwargs):
        if "default_bounds" in kwargs:
            raise TypeError(
                f"Cannot use 'default_bounds' with {self.__class__.__name__}."
            )
        
        
        if hasattr(self, "base_field"):
            self.base_field = self.base_field()
        super().__init__(*args, **kwargs)

    @property
    def model(self):
        try:
            return self.__dict__["model"]
        except KeyError:
            raise AttributeError(
                "'%s' object has no attribute 'model'" % self.__class__.__name__
            )

    @model.setter
    def model(self, model):
        self.__dict__["model"] = model
        self.base_field.model = model

    @classmethod
    def _choices_is_value(cls, value):
        return isinstance(value, (list, tuple)) or super()._choices_is_value(value)

    def get_placeholder(self, value, compiler, connection):
        return "%s::{}".format(self.db_type(connection))

    def get_prep_value(self, value):
        if value is None:
            return None
        elif isinstance(value, Range):
            return value
        elif isinstance(value, (list, tuple)):
            return self.range_type(value[0], value[1])
        return value

    def to_python(self, value):
        if isinstance(value, str):
            
            vals = json.loads(value)
            for end in ("lower", "upper"):
                if end in vals:
                    vals[end] = self.base_field.to_python(vals[end])
            value = self.range_type(**vals)
        elif isinstance(value, (list, tuple)):
            value = self.range_type(value[0], value[1])
        return value

    def set_attributes_from_name(self, name):
        super().set_attributes_from_name(name)
        self.base_field.set_attributes_from_name(name)

    def value_to_string(self, obj):
        value = self.value_from_object(obj)
        if value is None:
            return None
        if value.isempty:
            return json.dumps({"empty": True})
        base_field = self.base_field
        result = {"bounds": value._bounds}
        for end in ("lower", "upper"):
            val = getattr(value, end)
            if val is None:
                result[end] = None
            else:
                obj = AttributeSetter(base_field.attname, val)
                result[end] = base_field.value_to_string(obj)
        return json.dumps(result)

    def formfield(self, **kwargs):
        kwargs.setdefault("form_class", self.form_field)
        return super().formfield(**kwargs)


CANONICAL_RANGE_BOUNDS = "[)"


class ContinuousRangeField(RangeField):
    

    def __init__(self, *args, default_bounds=CANONICAL_RANGE_BOUNDS, **kwargs):
        if default_bounds not in ("[)", "(]", "()", "[]"):
            raise ValueError("default_bounds must be one of '[)', '(]', '()', or '[]'.")
        self.default_bounds = default_bounds
        super().__init__(*args, **kwargs)

    def get_prep_value(self, value):
        if isinstance(value, (list, tuple)):
            return self.range_type(value[0], value[1], self.default_bounds)
        return super().get_prep_value(value)

    def formfield(self, **kwargs):
        kwargs.setdefault("default_bounds", self.default_bounds)
        return super().formfield(**kwargs)

    def deconstruct(self):
        name, path, args, kwargs = super().deconstruct()
        if self.default_bounds and self.default_bounds != CANONICAL_RANGE_BOUNDS:
            kwargs["default_bounds"] = self.default_bounds
        return name, path, args, kwargs


class IntegerRangeField(RangeField):
    base_field = models.IntegerField
    range_type = NumericRange
    form_field = forms.IntegerRangeField

    def db_type(self, connection):
        return "int4range"


class BigIntegerRangeField(RangeField):
    base_field = models.BigIntegerField
    range_type = NumericRange
    form_field = forms.IntegerRangeField

    def db_type(self, connection):
        return "int8range"


class DecimalRangeField(ContinuousRangeField):
    base_field = models.DecimalField
    range_type = NumericRange
    form_field = forms.DecimalRangeField

    def db_type(self, connection):
        return "numrange"


class DateTimeRangeField(ContinuousRangeField):
    base_field = models.DateTimeField
    range_type = DateTimeTZRange
    form_field = forms.DateTimeRangeField

    def db_type(self, connection):
        return "tstzrange"


class DateRangeField(RangeField):
    base_field = models.DateField
    range_type = DateRange
    form_field = forms.DateRangeField

    def db_type(self, connection):
        return "daterange"


class RangeContains(lookups.DataContains):
    def get_prep_lookup(self):
        if not isinstance(self.rhs, (list, tuple, Range)):
            return Cast(self.rhs, self.lhs.field.base_field)
        return super().get_prep_lookup()


RangeField.register_lookup(RangeContains)
RangeField.register_lookup(lookups.ContainedBy)
RangeField.register_lookup(lookups.Overlap)


class DateTimeRangeContains(PostgresOperatorLookup):
    

    lookup_name = "contains"
    postgres_operator = RangeOperators.CONTAINS

    def process_rhs(self, compiler, connection):
        
        if isinstance(self.rhs, datetime.date):
            value = models.Value(self.rhs)
            self.rhs = value.resolve_expression(compiler.query)
        return super().process_rhs(compiler, connection)

    def as_postgresql(self, compiler, connection):
        sql, params = super().as_postgresql(compiler, connection)
        
        cast_sql = ""
        if (
            isinstance(self.rhs, models.Expression)
            and self.rhs._output_field_or_none
            and
            
            not isinstance(
                self.rhs._output_field_or_none, self.lhs.output_field.__class__
            )
        ):
            cast_internal_type = self.lhs.output_field.base_field.get_internal_type()
            cast_sql = "::{}".format(connection.data_types.get(cast_internal_type))
        return "%s%s" % (sql, cast_sql), params


DateRangeField.register_lookup(DateTimeRangeContains)
DateTimeRangeField.register_lookup(DateTimeRangeContains)


class RangeContainedBy(PostgresOperatorLookup):
    lookup_name = "contained_by"
    type_mapping = {
        "smallint": "int4range",
        "integer": "int4range",
        "bigint": "int8range",
        "double precision": "numrange",
        "numeric": "numrange",
        "date": "daterange",
        "timestamp with time zone": "tstzrange",
    }
    postgres_operator = RangeOperators.CONTAINED_BY

    def process_rhs(self, compiler, connection):
        rhs, rhs_params = super().process_rhs(compiler, connection)
        
        db_type = self.lhs.output_field.cast_db_type(connection).split("(")[0]
        cast_type = self.type_mapping[db_type]
        return "%s::%s" % (rhs, cast_type), rhs_params

    def process_lhs(self, compiler, connection):
        lhs, lhs_params = super().process_lhs(compiler, connection)
        if isinstance(self.lhs.output_field, models.FloatField):
            lhs = "%s::numeric" % lhs
        elif isinstance(self.lhs.output_field, models.SmallIntegerField):
            lhs = "%s::integer" % lhs
        return lhs, lhs_params

    def get_prep_lookup(self):
        return RangeField().get_prep_value(self.rhs)


models.DateField.register_lookup(RangeContainedBy)
models.DateTimeField.register_lookup(RangeContainedBy)
models.IntegerField.register_lookup(RangeContainedBy)
models.FloatField.register_lookup(RangeContainedBy)
models.DecimalField.register_lookup(RangeContainedBy)


@RangeField.register_lookup
class FullyLessThan(PostgresOperatorLookup):
    lookup_name = "fully_lt"
    postgres_operator = RangeOperators.FULLY_LT


@RangeField.register_lookup
class FullGreaterThan(PostgresOperatorLookup):
    lookup_name = "fully_gt"
    postgres_operator = RangeOperators.FULLY_GT


@RangeField.register_lookup
class NotLessThan(PostgresOperatorLookup):
    lookup_name = "not_lt"
    postgres_operator = RangeOperators.NOT_LT


@RangeField.register_lookup
class NotGreaterThan(PostgresOperatorLookup):
    lookup_name = "not_gt"
    postgres_operator = RangeOperators.NOT_GT


@RangeField.register_lookup
class AdjacentToLookup(PostgresOperatorLookup):
    lookup_name = "adjacent_to"
    postgres_operator = RangeOperators.ADJACENT_TO


@RangeField.register_lookup
class RangeStartsWith(models.Transform):
    lookup_name = "startswith"
    function = "lower"

    @property
    def output_field(self):
        return self.lhs.output_field.base_field


@RangeField.register_lookup
class RangeEndsWith(models.Transform):
    lookup_name = "endswith"
    function = "upper"

    @property
    def output_field(self):
        return self.lhs.output_field.base_field


@RangeField.register_lookup
class IsEmpty(models.Transform):
    lookup_name = "isempty"
    function = "isempty"
    output_field = models.BooleanField()


@RangeField.register_lookup
class LowerInclusive(models.Transform):
    lookup_name = "lower_inc"
    function = "LOWER_INC"
    output_field = models.BooleanField()


@RangeField.register_lookup
class LowerInfinite(models.Transform):
    lookup_name = "lower_inf"
    function = "LOWER_INF"
    output_field = models.BooleanField()


@RangeField.register_lookup
class UpperInclusive(models.Transform):
    lookup_name = "upper_inc"
    function = "UPPER_INC"
    output_field = models.BooleanField()


@RangeField.register_lookup
class UpperInfinite(models.Transform):
    lookup_name = "upper_inf"
    function = "UPPER_INF"
    output_field = models.BooleanField()