Model Fields
Custom Django model fields.
Postgres specific fields
Fields that can only be used with a Postgres database, enhancing the functionality provided by django.contrib.postgres.fields.
Ranges
Provides range fields that work
with the Range
classes from xocto.ranges.
The underlying Range type exposed by the Django ORM is psycopg2.extras.Range
which is awkward to
use throughout an application, and requires lots of boilerplate code to work with correctly.
Alternatively, xocto.ranges
are designed to be used throughout an application. These fields
make it possible to interact with them directly from the Django ORM.
Migrating from the Django range types to these range types is straightforward and does not require any changes to the database schema. The migration file generated will refer to the new fields, but since the underlying database type is the same, the migration will be a no-op.
The standard Django query operators are almost the same as for the built-in types.
They accept xocto.ranges
as arguments, but don’t support passing in a tuple of values:
assert SalesPeriod.objects.filter(
period__contains=ranges.FiniteDateRange(
start=datetime.date(2020, 1, 10),
end=datetime.date(2020, 1, 20),
)
).exists()
assert SalesPeriod.objects.filter(
period__overlaps=ranges.FiniteDateRange(
start=datetime.date(2020, 1, 10),
end=datetime.date(2020, 1, 20),
)
).exists()
# ERROR! This will raise a TypeError
SalesPeriod.objects.filter(period__overlaps=(datetime.date(2020, 1, 10), datetime.date(2020, 1, 20)))
FiniteDateRangeField
Module: xocto.fields.postgres.ranges.FiniteDateRangeField
Bounds: []
Type: xocto.ranges.FiniteDateRange
A field that represents an inclusive-inclusive []
ranges of dates. The start
and end of the range are inclusive and must not be None
.
import datetime
from django.db import models
from xocto import ranges
from xocto.fields.postgres import ranges as db_ranges
class SalesPeriod(models.Model):
...
period = db_ranges.FiniteDateRangeField()
sales_period = SalesPeriod.objects.create(
period=ranges.FiniteDateRange(
start=datetime.date(2020, 1, 1),
end=datetime.date(2020, 1, 31)
),
...
)
assert sales_period.period == ranges.FiniteDateRange(
start=datetime.date(2020, 1, 1),
end=datetime.date(2020, 1, 31)
)
assert sales_period.period.start == datetime.date(2020, 1, 1)
FiniteDateTimeRangeField
Module: xocto.fields.postgres.ranges.FiniteDateTimeRangeField
Bounds: [)
Type: xocto.ranges.FiniteDatetimeRange
A field that represents an inclusive-exclusive [)
ranges of timezone-aware
datetimes. Both the start and end of the range must not be None
.
The values returned from the database will always be converted to the local timezone
as per the TIME_ZONE
setting in settings.py
.
import datetime
from django.db import models
from xocto import ranges, localtime
from xocto.fields.postgres import ranges as db_ranges
class CalendarEntry(models.Model):
...
event_time = db_ranges.FiniteDateTimeRangeField()
calendar_entry = CalendarEntry.objects.create(
event_time=ranges.FiniteDatetimeRange(
start=localtime.datetime(2020, 1, 1, 14, 30),
end=localtime.datetime(2020, 1, 1, 15, 30)
),
...
)
assert calendar_entry.event_time == ranges.FiniteDatetimeRange(
start=localtime.datetime(2020, 1, 1, 14, 30),
end=localtime.datetime(2020, 1, 1, 15, 30)
)
assert calendar_entry.event_time.start == localtime.datetime(2020, 1, 1, 14, 30)
HalfFiniteDateTimeRangeField
Module: xocto.fields.postgres.ranges.HalfFiniteDateTimeRangeField
Bounds: [)
Type: xocto.ranges.HalfFiniteDatetimeRange
NOTE: docs can not link directly to
HalfFiniteDatetimeRange
at this stage as it’s a type alias
A field that represents an inclusive-exclusive [)
ranges of timezone-aware
datetimes. The end of the range may be open-ended, represented by None
.
The values returned from the database will always be converted to the local timezone
as per the TIME_ZONE
setting in settings.py
.
import datetime
from django.db import models
from xocto import ranges, localtime
from xocto.fields.postgres import ranges as db_ranges
class Agreement(models.Model):
...
period = db_ranges.HalfFiniteDateTimeRangeField()
agreement = Agreement.objects.create(
period=ranges.HalfFiniteDatetimeRange(
start=localtime.datetime(2020, 1, 1, 14, 30),
end=None,
),
...
)
assert agreement.period == ranges.HalfFiniteDatetimeRange(
start=localtime.datetime(2020, 1, 1, 14, 30),
end=None
)
assert agreement.period.start == localtime.datetime(2020, 1, 1, 14, 30)