Loading django/contrib/postgres/fields/ranges.py +32 −0 Original line number Diff line number Diff line Loading @@ -98,6 +98,38 @@ RangeField.register_lookup(lookups.ContainedBy) RangeField.register_lookup(lookups.Overlap) class RangeContainedBy(models.Lookup): lookup_name = 'contained_by' type_mapping = { 'integer': 'int4range', 'bigint': 'int8range', 'double precision': 'numrange', 'date': 'daterange', 'timestamp with time zone': 'tstzrange', } def as_sql(self, qn, connection): field = self.lhs.output_field if isinstance(field, models.FloatField): sql = '%s::numeric <@ %s::{}'.format(self.type_mapping[field.db_type(connection)]) else: sql = '%s <@ %s::{}'.format(self.type_mapping[field.db_type(connection)]) lhs, lhs_params = self.process_lhs(qn, connection) rhs, rhs_params = self.process_rhs(qn, connection) params = lhs_params + rhs_params return sql % (lhs, rhs), params def get_prep_lookup(self): return RangeField().get_prep_lookup(self.lookup_name, self.rhs) models.DateField.register_lookup(RangeContainedBy) models.DateTimeField.register_lookup(RangeContainedBy) models.IntegerField.register_lookup(RangeContainedBy) models.BigIntegerField.register_lookup(RangeContainedBy) models.FloatField.register_lookup(RangeContainedBy) @RangeField.register_lookup class FullyLessThan(lookups.PostgresSimpleLookup): lookup_name = 'fully_lt' Loading docs/ref/contrib/postgres/fields.txt +22 −2 Original line number Diff line number Diff line Loading @@ -631,14 +631,18 @@ model:: class Event(models.Model): name = models.CharField(max_length=200) ages = IntegerRangeField() start = models.DateTimeField() 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)) >>> import datetime >>> from django.utils import timezone >>> now = timezone.now() >>> Event.objects.create(name='Soft play', ages=(0, 10), start=now) >>> Event.objects.create(name='Pub trip', ages=(21, None), start=now - datetime.timedelta(days=1)) and ``NumericRange``: Loading Loading @@ -667,6 +671,22 @@ contained_by >>> Event.objects.filter(ages__contained_by=NumericRange(0, 15)) [<Event: Soft play>] .. versionadded 1.9 The `contained_by` lookup is also available on the non-range field types: :class:`~django.db.models.fields.IntegerField`, :class:`~django.db.models.fields.BigIntegerField`, :class:`~django.db.models.fields.FloatField`, :class:`~django.db.models.fields.DateField`, and :class:`~django.db.models.fields.DateTimeField`. For example:: >>> from psycopg2.extras import DateTimeTZRange >>> Event.objects.filter(start__contained_by=DateTimeTZRange( ... timezone.now() - datetime.timedelta(hours=1), ... timezone.now() + datetime.timedelta(hours=1), ... ) [<Event: Soft play>] .. fieldlookup:: rangefield.overlap overlap Loading docs/releases/1.9.txt +2 −0 Original line number Diff line number Diff line Loading @@ -91,6 +91,8 @@ Minor features :mod:`django.contrib.postgres` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * Added support for the :lookup:`rangefield.contained_by` lookup for some built in fields which correspond to the range fields. * Added :class:`~django.contrib.postgres.fields.JSONField`. * Added :doc:`/ref/contrib/postgres/aggregates`. Loading tests/postgres_tests/migrations/0002_create_test_models.py +15 −0 Original line number Diff line number Diff line Loading @@ -143,6 +143,21 @@ class Migration(migrations.Migration): ('timestamps', DateTimeRangeField(null=True, blank=True)), ('dates', DateRangeField(null=True, blank=True)), ], options={ 'required_db_vendor': 'postgresql' }, bases=(models.Model,) ), migrations.CreateModel( name='RangeLookupsModel', fields=[ ('parent', models.ForeignKey('postgres_tests.RangesModel', blank=True, null=True)), ('integer', models.IntegerField(blank=True, null=True)), ('big_integer', models.BigIntegerField(blank=True, null=True)), ('float', models.FloatField(blank=True, null=True)), ('timestamp', models.DateTimeField(blank=True, null=True)), ('date', models.DateField(blank=True, null=True)), ], options={ 'required_db_vendor': 'postgresql', }, Loading tests/postgres_tests/models.py +12 −0 Original line number Diff line number Diff line Loading @@ -60,11 +60,23 @@ if connection.vendor == 'postgresql' and connection.pg_version >= 90200: floats = FloatRangeField(blank=True, null=True) timestamps = DateTimeRangeField(blank=True, null=True) dates = DateRangeField(blank=True, null=True) class RangeLookupsModel(PostgreSQLModel): parent = models.ForeignKey(RangesModel, blank=True, null=True) integer = models.IntegerField(blank=True, null=True) big_integer = models.BigIntegerField(blank=True, null=True) float = models.FloatField(blank=True, null=True) timestamp = models.DateTimeField(blank=True, null=True) date = models.DateField(blank=True, null=True) else: # create an object with this name so we don't have failing imports class RangesModel(object): pass class RangeLookupsModel(object): pass # Only create this model for postgres >= 9.4 if connection.vendor == 'postgresql' and connection.pg_version >= 90400: Loading Loading
django/contrib/postgres/fields/ranges.py +32 −0 Original line number Diff line number Diff line Loading @@ -98,6 +98,38 @@ RangeField.register_lookup(lookups.ContainedBy) RangeField.register_lookup(lookups.Overlap) class RangeContainedBy(models.Lookup): lookup_name = 'contained_by' type_mapping = { 'integer': 'int4range', 'bigint': 'int8range', 'double precision': 'numrange', 'date': 'daterange', 'timestamp with time zone': 'tstzrange', } def as_sql(self, qn, connection): field = self.lhs.output_field if isinstance(field, models.FloatField): sql = '%s::numeric <@ %s::{}'.format(self.type_mapping[field.db_type(connection)]) else: sql = '%s <@ %s::{}'.format(self.type_mapping[field.db_type(connection)]) lhs, lhs_params = self.process_lhs(qn, connection) rhs, rhs_params = self.process_rhs(qn, connection) params = lhs_params + rhs_params return sql % (lhs, rhs), params def get_prep_lookup(self): return RangeField().get_prep_lookup(self.lookup_name, self.rhs) models.DateField.register_lookup(RangeContainedBy) models.DateTimeField.register_lookup(RangeContainedBy) models.IntegerField.register_lookup(RangeContainedBy) models.BigIntegerField.register_lookup(RangeContainedBy) models.FloatField.register_lookup(RangeContainedBy) @RangeField.register_lookup class FullyLessThan(lookups.PostgresSimpleLookup): lookup_name = 'fully_lt' Loading
docs/ref/contrib/postgres/fields.txt +22 −2 Original line number Diff line number Diff line Loading @@ -631,14 +631,18 @@ model:: class Event(models.Model): name = models.CharField(max_length=200) ages = IntegerRangeField() start = models.DateTimeField() 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)) >>> import datetime >>> from django.utils import timezone >>> now = timezone.now() >>> Event.objects.create(name='Soft play', ages=(0, 10), start=now) >>> Event.objects.create(name='Pub trip', ages=(21, None), start=now - datetime.timedelta(days=1)) and ``NumericRange``: Loading Loading @@ -667,6 +671,22 @@ contained_by >>> Event.objects.filter(ages__contained_by=NumericRange(0, 15)) [<Event: Soft play>] .. versionadded 1.9 The `contained_by` lookup is also available on the non-range field types: :class:`~django.db.models.fields.IntegerField`, :class:`~django.db.models.fields.BigIntegerField`, :class:`~django.db.models.fields.FloatField`, :class:`~django.db.models.fields.DateField`, and :class:`~django.db.models.fields.DateTimeField`. For example:: >>> from psycopg2.extras import DateTimeTZRange >>> Event.objects.filter(start__contained_by=DateTimeTZRange( ... timezone.now() - datetime.timedelta(hours=1), ... timezone.now() + datetime.timedelta(hours=1), ... ) [<Event: Soft play>] .. fieldlookup:: rangefield.overlap overlap Loading
docs/releases/1.9.txt +2 −0 Original line number Diff line number Diff line Loading @@ -91,6 +91,8 @@ Minor features :mod:`django.contrib.postgres` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * Added support for the :lookup:`rangefield.contained_by` lookup for some built in fields which correspond to the range fields. * Added :class:`~django.contrib.postgres.fields.JSONField`. * Added :doc:`/ref/contrib/postgres/aggregates`. Loading
tests/postgres_tests/migrations/0002_create_test_models.py +15 −0 Original line number Diff line number Diff line Loading @@ -143,6 +143,21 @@ class Migration(migrations.Migration): ('timestamps', DateTimeRangeField(null=True, blank=True)), ('dates', DateRangeField(null=True, blank=True)), ], options={ 'required_db_vendor': 'postgresql' }, bases=(models.Model,) ), migrations.CreateModel( name='RangeLookupsModel', fields=[ ('parent', models.ForeignKey('postgres_tests.RangesModel', blank=True, null=True)), ('integer', models.IntegerField(blank=True, null=True)), ('big_integer', models.BigIntegerField(blank=True, null=True)), ('float', models.FloatField(blank=True, null=True)), ('timestamp', models.DateTimeField(blank=True, null=True)), ('date', models.DateField(blank=True, null=True)), ], options={ 'required_db_vendor': 'postgresql', }, Loading
tests/postgres_tests/models.py +12 −0 Original line number Diff line number Diff line Loading @@ -60,11 +60,23 @@ if connection.vendor == 'postgresql' and connection.pg_version >= 90200: floats = FloatRangeField(blank=True, null=True) timestamps = DateTimeRangeField(blank=True, null=True) dates = DateRangeField(blank=True, null=True) class RangeLookupsModel(PostgreSQLModel): parent = models.ForeignKey(RangesModel, blank=True, null=True) integer = models.IntegerField(blank=True, null=True) big_integer = models.BigIntegerField(blank=True, null=True) float = models.FloatField(blank=True, null=True) timestamp = models.DateTimeField(blank=True, null=True) date = models.DateField(blank=True, null=True) else: # create an object with this name so we don't have failing imports class RangesModel(object): pass class RangeLookupsModel(object): pass # Only create this model for postgres >= 9.4 if connection.vendor == 'postgresql' and connection.pg_version >= 90400: Loading