mirror of
https://github.com/django/django.git
synced 2025-08-04 02:48:35 +00:00
Fixed #24001 -- Added range fields for PostgreSQL.
Added support for PostgreSQL range types to contrib.postgres. - 5 new model fields - 4 new form fields - New validators - Uses psycopg2's range type implementation in python
This commit is contained in:
parent
916e38802f
commit
48ad288679
15 changed files with 986 additions and 6 deletions
|
@ -402,3 +402,266 @@ using in conjunction with lookups on
|
|||
|
||||
>>> Dog.objects.filter(data__values__contains=['collie'])
|
||||
[<Dog: Meg>]
|
||||
|
||||
.. _range-fields:
|
||||
|
||||
Range Fields
|
||||
------------
|
||||
|
||||
There are five range field types, corresponding to the built-in range types in
|
||||
PostgreSQL. These fields are used to store a range of values; for example the
|
||||
start and end timestamps of an event, or the range of ages an activity is
|
||||
suitable for.
|
||||
|
||||
All of the range fields translate to :ref:`psycopg2 Range objects
|
||||
<psycopg2:adapt-range>` in python, but also accept tuples as input if no bounds
|
||||
information is necessary. The default is lower bound included, upper bound
|
||||
excluded.
|
||||
|
||||
IntegerRangeField
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. class:: IntegerRangeField(**options)
|
||||
|
||||
Stores a range of integers. Based on an
|
||||
:class:`~django.db.models.IntegerField`. Represented by an ``int4range`` in
|
||||
the database and a :class:`~psycopg2:psycopg2.extras.NumericRange` in
|
||||
Python.
|
||||
|
||||
BigIntegerRangeField
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. class:: BigIntegerRangeField(**options)
|
||||
|
||||
Stores a range of large integers. Based on a
|
||||
:class:`~django.db.models.BigIntegerField`. Represented by an ``int8range``
|
||||
in the database and a :class:`~psycopg2:psycopg2.extras.NumericRange` in
|
||||
Python.
|
||||
|
||||
FloatRangeField
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
.. class:: FloatRangeField(**options)
|
||||
|
||||
Stores a range of floating point values. Based on a
|
||||
:class:`~django.db.models.FloatField`. Represented by a ``numrange`` in the
|
||||
database and a :class:`~psycopg2:psycopg2.extras.NumericRange` in Python.
|
||||
|
||||
DateTimeRangeField
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. class:: DateTimeRangeField(**options)
|
||||
|
||||
Stores a range of timestamps. Based on a
|
||||
:class:`~django.db.models.DateTimeField`. Represented by a ``tztsrange`` in
|
||||
the database and a :class:`~psycopg2:psycopg2.extras.DateTimeTZRange` in
|
||||
Python.
|
||||
|
||||
DateRangeField
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
.. class:: DateRangeField(**options)
|
||||
|
||||
Stores a range of dates. Based on a
|
||||
:class:`~django.db.models.DateField`. Represented by a ``daterange`` in the
|
||||
database and a :class:`~psycopg2:psycopg2.extras.DateRange` in Python.
|
||||
|
||||
Querying Range Fields
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
There are a number of custom lookups and transforms for range fields. They are
|
||||
available on all the above fields, but we will use the following example
|
||||
model::
|
||||
|
||||
from django.contrib.postgres.fields import IntegerRangeField
|
||||
from django.db import models
|
||||
|
||||
class Event(models.Model):
|
||||
name = models.CharField(max_length=200)
|
||||
ages = IntegerRangeField()
|
||||
|
||||
def __str__(self): # __unicode__ on Python 2
|
||||
return self.name
|
||||
|
||||
We will also use the following example objects::
|
||||
|
||||
>>> Event.objects.create(name='Soft play', ages=(0, 10))
|
||||
>>> Event.objects.create(name='Pub trip', ages=(21, None))
|
||||
|
||||
and ``NumericRange``:
|
||||
|
||||
>>> from psycopg2.extras import NumericRange
|
||||
|
||||
Containment functions
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As with other PostgreSQL fields, there are three standard containment
|
||||
operators: ``contains``, ``contained_by`` and ``overlap``, using the SQL
|
||||
operators ``@>``, ``<@``, and ``&&`` respectively.
|
||||
|
||||
.. fieldlookup:: rangefield.contains
|
||||
|
||||
contains
|
||||
''''''''
|
||||
|
||||
>>> Event.objects.filter(ages__contains=NumericRange(4, 5))
|
||||
[<Event: Soft play>]
|
||||
|
||||
.. fieldlookup:: rangefield.contained_by
|
||||
|
||||
contained_by
|
||||
''''''''''''
|
||||
|
||||
>>> Event.objects.filter(ages__contained_by=NumericRange(0, 15))
|
||||
[<Event: Soft play>]
|
||||
|
||||
.. fieldlookup:: rangefield.overlap
|
||||
|
||||
overlap
|
||||
'''''''
|
||||
|
||||
>>> Event.objects.filter(ages__overlap=NumericRange(8, 12))
|
||||
[<Event: Soft play>]
|
||||
|
||||
Comparison functions
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Range fields support the standard lookups: :lookup:`lt`, :lookup:`gt`,
|
||||
:lookup:`lte` and :lookup:`gte`. These are not particularly helpful - they
|
||||
compare the lower bounds first and then the upper bounds only if necessary.
|
||||
This is also the strategy used to order by a range field. It is better to use
|
||||
the specific range comparison operators.
|
||||
|
||||
.. fieldlookup:: rangefield.fully_lt
|
||||
|
||||
fully_lt
|
||||
''''''''
|
||||
|
||||
The returned ranges are strictly less than the passed range. In other words,
|
||||
all the points in the returned range are less than all those in the passed
|
||||
range.
|
||||
|
||||
>>> Event.objects.filter(ages__fully_lt=NumericRange(11, 15))
|
||||
[<Event: Soft play>]
|
||||
|
||||
.. fieldlookup:: rangefield.fully_gt
|
||||
|
||||
fully_gt
|
||||
''''''''
|
||||
|
||||
The returned ranges are strictly greater than the passed range. In other words,
|
||||
the all the points in the returned range are greater than all those in the
|
||||
passed range.
|
||||
|
||||
>>> Event.objects.filter(ages__fully_gt=NumericRange(11, 15))
|
||||
[<Event: Pub trip>]
|
||||
|
||||
.. fieldlookup:: rangefield.not_lt
|
||||
|
||||
not_lt
|
||||
''''''
|
||||
|
||||
The returned ranges do not contain any points less than the passed range, that
|
||||
is the lower bound of the returned range is at least the lower bound of the
|
||||
passed range.
|
||||
|
||||
>>> Event.objects.filter(ages__not_lt=NumericRange(0, 15))
|
||||
[<Event: Soft play>, <Event: Pub trip>]
|
||||
|
||||
.. fieldlookup:: rangefield.not_gt
|
||||
|
||||
not_gt
|
||||
''''''
|
||||
|
||||
The returned ranges do not contain any points greater than the passed range, that
|
||||
is the upper bound of the returned range is at most the upper bound of the
|
||||
passed range.
|
||||
|
||||
>>> Event.objects.filter(ages__not_gt=NumericRange(3, 10))
|
||||
[<Event: Soft play>]
|
||||
|
||||
.. fieldlookup:: rangefield.adjacent_to
|
||||
|
||||
adjacent_to
|
||||
'''''''''''
|
||||
|
||||
The returned ranges share a bound with the passed range.
|
||||
|
||||
>>> Event.objects.filter(ages__adjacent_to=NumericRange(10, 21))
|
||||
[<Event: Soft play>, <Event: Pub trip>]
|
||||
|
||||
Querying using the bounds
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
There are three transforms available for use in queries. You can extract the
|
||||
lower or upper bound, or query based on emptiness.
|
||||
|
||||
.. fieldlookup:: rangefield.startswith
|
||||
|
||||
startswith
|
||||
''''''''''
|
||||
|
||||
Returned objects have the given lower bound. Can be chained to valid lookups
|
||||
for the base field.
|
||||
|
||||
>>> Event.objects.filter(ages__startswith=21)
|
||||
[<Event: Pub trip>]
|
||||
|
||||
.. fieldlookup:: rangefield.endswith
|
||||
|
||||
endswith
|
||||
''''''''
|
||||
|
||||
Returned objects have the given upper bound. Can be chained to valid lookups
|
||||
for the base field.
|
||||
|
||||
>>> Event.objects.filter(ages__endswith=10)
|
||||
[<Event: Soft play>]
|
||||
|
||||
.. fieldlookup:: rangefield.isempty
|
||||
|
||||
isempty
|
||||
'''''''
|
||||
|
||||
Returned objects are empty ranges. Can be chained to valid lookups for a
|
||||
:class:`~django.db.models.BooleanField`.
|
||||
|
||||
>>> Event.objects.filter(ages__isempty=True)
|
||||
[]
|
||||
|
||||
Defining your own range types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
PostgreSQL allows the definition of custom range types. Django's model and form
|
||||
field implementations use base classes below, and psycopg2 provides a
|
||||
:func:`~psycopg2:psycopg2.extras.register_range` to allow use of custom range
|
||||
types.
|
||||
|
||||
.. class:: RangeField(**options)
|
||||
|
||||
Base class for model range fields.
|
||||
|
||||
.. attribute:: base_field
|
||||
|
||||
The model field to use.
|
||||
|
||||
.. attribute:: range_type
|
||||
|
||||
The psycopg2 range type to use.
|
||||
|
||||
.. attribute:: form_field
|
||||
|
||||
The form field class to use. Should be a sublcass of
|
||||
:class:`django.contrib.postgres.forms.BaseRangeField`.
|
||||
|
||||
.. class:: django.contrib.postgres.forms.BaseRangeField
|
||||
|
||||
Base class for form range fields.
|
||||
|
||||
.. attribute:: base_field
|
||||
|
||||
The form field to use.
|
||||
|
||||
.. attribute:: range_type
|
||||
|
||||
The psycopg2 range type to use.
|
||||
|
|
|
@ -154,3 +154,48 @@ HStoreField
|
|||
On occasions it may be useful to require or restrict the keys which are
|
||||
valid for a given field. This can be done using the
|
||||
:class:`~django.contrib.postgres.validators.KeysValidator`.
|
||||
|
||||
Range Fields
|
||||
------------
|
||||
|
||||
This group of fields all share similar functionality for accepting range data.
|
||||
They are based on :class:`~django.forms.MultiValueField`. They treat one
|
||||
omitted value as an unbounded range. They also validate that the lower bound is
|
||||
not greater than the upper bound.
|
||||
|
||||
IntegerRangeField
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. class:: IntegerRangeField
|
||||
|
||||
Based on :class:`~django.forms.IntegerField` and translates its input into
|
||||
:class:`~psycopg2:psycopg2.extras.NumericRange`. Default for
|
||||
:class:`~django.contrib.postgres.fields.IntegerRangeField` and
|
||||
:class:`~django.contrib.postgres.fields.BigIntegerRangeField`.
|
||||
|
||||
FloatRangeField
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
.. class:: FloatRangeField
|
||||
|
||||
Based on :class:`~django.forms.FloatField` and translates its input into
|
||||
:class:`~psycopg2:psycopg2.extras.NumericRange`. Default for
|
||||
:class:`~django.contrib.postgres.fields.FloatRangeField`.
|
||||
|
||||
DateTimeRangeField
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. class:: DateTimeRangeField
|
||||
|
||||
Based on :class:`~django.forms.DateTimeField` and translates its input into
|
||||
:class:`~psycopg2:psycopg2.extras.DateTimeTZRange`. Default for
|
||||
:class:`~django.contrib.postgres.fields.DateTimeRangeField`.
|
||||
|
||||
DateRangeField
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
.. class:: DateRangeField
|
||||
|
||||
Based on :class:`~django.forms.DateField` and translates its input into
|
||||
:class:`~psycopg2:psycopg2.extras.DateRange`. Default for
|
||||
:class:`~django.contrib.postgres.fields.DateRangeField`.
|
||||
|
|
|
@ -18,3 +18,16 @@ Validators
|
|||
.. note::
|
||||
Note that this checks only for the existence of a given key, not that
|
||||
the value of a key is non-empty.
|
||||
|
||||
Range validators
|
||||
----------------
|
||||
|
||||
.. class:: RangeMaxValueValidator(limit_value, message=None)
|
||||
|
||||
Validates that the upper bound of the range is not greater than
|
||||
``limit_value``.
|
||||
|
||||
.. class:: RangeMinValueValidator(limit_value, message=None)
|
||||
|
||||
Validates that the lower bound of the range is not less than the
|
||||
``limit_value``.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue