django-postgres-multirange
PostgreSQL multirange fields for Django, built on the same primitives as django.contrib.postgres range fields.
A multirange is an ordered set of non-overlapping ranges — the native way to store things like available hours, blocked-out dates, or VLAN id spans in a single column.
Django declined to add these types to contrib.postgres. This package fills that gap.
Requirements
- Python 3.10+
- Django 5.2+
- psycopg 3
- PostgreSQL 14+ (developed against PostgreSQL 17)
Install
pip install django-postgres-multirange
# or, with the binary psycopg build:
pip install "django-postgres-multirange[binary]"
Add django.contrib.postgres to INSTALLED_APPS. Adding django_postgres_multirange is optional; the fields work without it.
Fields
Each range field Django already ships has a matching multirange:
| Field | PostgreSQL type | Element |
|---|---|---|
IntegerMultiRangeField |
int4multirange |
int4 / IntegerRangeField |
BigIntegerMultiRangeField |
int8multirange |
int8 / BigIntegerRangeField |
DecimalMultiRangeField |
nummultirange |
numeric / DecimalRangeField |
DateTimeMultiRangeField |
tstzmultirange |
timestamptz / DateTimeRangeField |
DateMultiRangeField |
datemultirange |
date / DateRangeField |
Python values are psycopg.types.multirange.Multirange objects — a mutable sequence of Range. Lists of ranges or (lower, upper) pairs are accepted on assignment, the same way RangeField accepts a pair.
from django.contrib.postgres.indexes import GistIndex
from django.db import models
from django_postgres_multirange import IntegerMultiRangeField, Multirange
from psycopg.types.range import Range
class Schedule(models.Model):
hours = IntegerMultiRangeField()
class Meta:
indexes = [GistIndex(fields=["hours"])]
Schedule.objects.create(hours=[(9, 12), (13, 17)])
row = Schedule.objects.get()
# PostgreSQL normalizes on write.
assert row.hours == Multirange([Range(9, 12), Range(13, 17)])
Continuous fields (DecimalMultiRangeField, DateTimeMultiRangeField) accept Django's default_bounds ("[)" by default) for pair input.
Empty ({} / Multirange()) is distinct from NULL.
Lookups
The lookups match RangeField, plus two that only make sense on a multirange:
| Lookup | Operator / function |
|---|---|
contains |
@> element, range, or multirange |
contained_by |
<@ |
overlap |
&& |
fully_lt |
<< |
fully_gt |
>> |
not_lt |
&> |
not_gt |
&< |
adjacent_to |
`- |
startswith / endswith |
lower() / upper() of the whole multirange |
isempty |
isempty() |
lower_inc, lower_inf, upper_inc, upper_inf |
bound tests |
range_merge |
smallest covering range |
len |
number of ranges |
Schedule.objects.filter(hours__contains=10)
Schedule.objects.filter(hours__overlap=(12, 14))
Schedule.objects.filter(hours__contains=[(9, 10), (14, 15)])
Schedule.objects.filter(hours__range_merge__contains=16)
Schedule.objects.filter(hours__len=2)
GiST indexes and ExclusionConstraint work the same way they do for range fields (&&, @>, <@, …).
Development
make up # Postgres 17 on localhost:54317
make test
See CONTRIBUTING.md for the full workflow.
Roadmap
Not in this release:
tsmultirange(Django has notsrangecounterpart)- union / intersection / difference /
unnestdatabase functions range_agg/range_intersect_agg- user-defined range types
- a non-JSON admin widget
License
Questions or issues: omar.bohsali@gmail.com
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_postgres_multirange-0.1.0.tar.gz.
File metadata
- Download URL: django_postgres_multirange-0.1.0.tar.gz
- Upload date:
- Size: 15.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1aeb864950b5beb5e39db979fea1676a650bc8693010a15d55b0dc390df0a717
|
|
| MD5 |
58616595b03f0365d3bbec151a48010c
|
|
| BLAKE2b-256 |
0c7afbdb12acbd8f97faf0722e637fb41705400a834b18e19119b9b36867cdf6
|
Provenance
The following attestation bundles were made for django_postgres_multirange-0.1.0.tar.gz:
Publisher:
publish.yml on omarish/django-postgres-multirange
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_postgres_multirange-0.1.0.tar.gz -
Subject digest:
1aeb864950b5beb5e39db979fea1676a650bc8693010a15d55b0dc390df0a717 - Sigstore transparency entry: 2471024783
- Sigstore integration time:
-
Permalink:
omarish/django-postgres-multirange@2000b1644f667a601f3ec2540fdb1c3ba5decf7c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/omarish
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2000b1644f667a601f3ec2540fdb1c3ba5decf7c -
Trigger Event:
release
-
Statement type:
File details
Details for the file django_postgres_multirange-0.1.0-py3-none-any.whl.
File metadata
- Download URL: django_postgres_multirange-0.1.0-py3-none-any.whl
- Upload date:
- Size: 12.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8539104cd3e58c80abc6d17f2f6a181d855718403a219c09375f1e5ce3a698a2
|
|
| MD5 |
7000a96b0866dc6500daef1b0f517e65
|
|
| BLAKE2b-256 |
1fe3096c999089de1f306d2d0a64c403f1fbd9c97d33b52f0ee0b32b286207b7
|
Provenance
The following attestation bundles were made for django_postgres_multirange-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on omarish/django-postgres-multirange
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_postgres_multirange-0.1.0-py3-none-any.whl -
Subject digest:
8539104cd3e58c80abc6d17f2f6a181d855718403a219c09375f1e5ce3a698a2 - Sigstore transparency entry: 2471024886
- Sigstore integration time:
-
Permalink:
omarish/django-postgres-multirange@2000b1644f667a601f3ec2540fdb1c3ba5decf7c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/omarish
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2000b1644f667a601f3ec2540fdb1c3ba5decf7c -
Trigger Event:
release
-
Statement type: