Fixed #22316 -- Added time filters to TimeField on SQLite.
This was implemented for non-SQLite backends in 1.7 (as a side effect of #16187).
This commit is contained in:
parent
6700c90935
commit
2dc93bb10a
|
@ -122,6 +122,13 @@ class BaseDatabaseOperations(object):
|
|||
"""
|
||||
raise NotImplementedError('subclasses of BaseDatabaseOperations may require a datetime_trunk_sql() method')
|
||||
|
||||
def time_extract_sql(self, lookup_type, field_name):
|
||||
"""
|
||||
Given a lookup_type of 'hour', 'minute' or 'second', returns the SQL
|
||||
that extracts a value from the given time field field_name.
|
||||
"""
|
||||
return self.date_extract_sql(lookup_type, field_name)
|
||||
|
||||
def deferrable_sql(self):
|
||||
"""
|
||||
Returns the SQL necessary to make a constraint "initially deferred"
|
||||
|
|
|
@ -210,6 +210,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
|||
conn.create_function("django_datetime_cast_date", 2, _sqlite_datetime_cast_date)
|
||||
conn.create_function("django_datetime_extract", 3, _sqlite_datetime_extract)
|
||||
conn.create_function("django_datetime_trunc", 3, _sqlite_datetime_trunc)
|
||||
conn.create_function("django_time_extract", 2, _sqlite_time_extract)
|
||||
conn.create_function("regexp", 2, _sqlite_regexp)
|
||||
conn.create_function("django_format_dtdelta", 3, _sqlite_format_dtdelta)
|
||||
conn.create_function("django_power", 2, _sqlite_power)
|
||||
|
@ -402,6 +403,16 @@ def _sqlite_datetime_trunc(lookup_type, dt, tzname):
|
|||
return "%i-%02i-%02i %02i:%02i:%02i" % (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second)
|
||||
|
||||
|
||||
def _sqlite_time_extract(lookup_type, dt):
|
||||
if dt is None:
|
||||
return None
|
||||
try:
|
||||
dt = backend_utils.typecast_time(dt)
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
return getattr(dt, lookup_type)
|
||||
|
||||
|
||||
def _sqlite_format_dtdelta(conn, lhs, rhs):
|
||||
"""
|
||||
LHS and RHS can be either:
|
||||
|
|
|
@ -88,6 +88,13 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|||
return "django_datetime_trunc('%s', %s, %%s)" % (
|
||||
lookup_type.lower(), field_name), [tzname]
|
||||
|
||||
def time_extract_sql(self, lookup_type, field_name):
|
||||
# sqlite doesn't support extract, so we fake it with the user-defined
|
||||
# function django_time_extract that's registered in connect(). Note that
|
||||
# single quotes are used because this is a string (and could otherwise
|
||||
# cause a collision with a field name).
|
||||
return "django_time_extract('%s', %s)" % (lookup_type.lower(), field_name)
|
||||
|
||||
def drop_foreignkey_sql(self):
|
||||
return ""
|
||||
|
||||
|
|
|
@ -2416,9 +2416,12 @@ class DateTransform(Transform):
|
|||
tzname = timezone.get_current_timezone_name() if settings.USE_TZ else None
|
||||
sql, tz_params = connection.ops.datetime_extract_sql(self.lookup_name, sql, tzname)
|
||||
params.extend(tz_params)
|
||||
else:
|
||||
# DateField and TimeField.
|
||||
elif isinstance(lhs_output_field, DateField):
|
||||
sql = connection.ops.date_extract_sql(self.lookup_name, sql)
|
||||
elif isinstance(lhs_output_field, TimeField):
|
||||
sql = connection.ops.time_extract_sql(self.lookup_name, sql)
|
||||
else:
|
||||
raise ValueError('DateTransform only valid on Date/Time/DateTimeFields')
|
||||
return sql, params
|
||||
|
||||
@cached_property
|
||||
|
|
|
@ -2606,23 +2606,30 @@ in the database <database-time-zone-definitions>`.
|
|||
hour
|
||||
~~~~
|
||||
|
||||
For datetime fields, an exact hour match. Allows chaining additional field
|
||||
lookups. Takes an integer between 0 and 23.
|
||||
For datetime and time fields, an exact hour match. Allows chaining additional
|
||||
field lookups. Takes an integer between 0 and 23.
|
||||
|
||||
Example::
|
||||
|
||||
Event.objects.filter(timestamp__hour=23)
|
||||
Event.objects.filter(time__hour=5)
|
||||
Event.objects.filter(timestamp__hour__gte=12)
|
||||
|
||||
SQL equivalent::
|
||||
|
||||
SELECT ... WHERE EXTRACT('hour' FROM timestamp) = '23';
|
||||
SELECT ... WHERE EXTRACT('hour' FROM time) = '5';
|
||||
SELECT ... WHERE EXTRACT('hour' FROM timestamp) >= '12';
|
||||
|
||||
(The exact SQL syntax varies for each database engine.)
|
||||
|
||||
When :setting:`USE_TZ` is ``True``, values are converted to the current time
|
||||
zone before filtering.
|
||||
For datetime fields, when :setting:`USE_TZ` is ``True``, values are converted
|
||||
to the current time zone before filtering.
|
||||
|
||||
.. versionchanged:: 1.9
|
||||
|
||||
Added support for :class:`~django.db.models.TimeField` on SQLite (other
|
||||
databases supported it as of 1.7).
|
||||
|
||||
.. versionchanged:: 1.9
|
||||
|
||||
|
@ -2633,23 +2640,30 @@ zone before filtering.
|
|||
minute
|
||||
~~~~~~
|
||||
|
||||
For datetime fields, an exact minute match. Allows chaining additional field
|
||||
lookups. Takes an integer between 0 and 59.
|
||||
For datetime and time fields, an exact minute match. Allows chaining additional
|
||||
field lookups. Takes an integer between 0 and 59.
|
||||
|
||||
Example::
|
||||
|
||||
Event.objects.filter(timestamp__minute=29)
|
||||
Event.objects.filter(time__minute=46)
|
||||
Event.objects.filter(timestamp__minute__gte=29)
|
||||
|
||||
SQL equivalent::
|
||||
|
||||
SELECT ... WHERE EXTRACT('minute' FROM timestamp) = '29';
|
||||
SELECT ... WHERE EXTRACT('minute' FROM time) = '46';
|
||||
SELECT ... WHERE EXTRACT('minute' FROM timestamp) >= '29';
|
||||
|
||||
(The exact SQL syntax varies for each database engine.)
|
||||
|
||||
When :setting:`USE_TZ` is ``True``, values are converted to the current time
|
||||
zone before filtering.
|
||||
For datetime fields, When :setting:`USE_TZ` is ``True``, values are converted
|
||||
to the current time zone before filtering.
|
||||
|
||||
.. versionchanged:: 1.9
|
||||
|
||||
Added support for :class:`~django.db.models.TimeField` on SQLite (other
|
||||
databases supported it as of 1.7).
|
||||
|
||||
.. versionchanged:: 1.9
|
||||
|
||||
|
@ -2660,23 +2674,30 @@ zone before filtering.
|
|||
second
|
||||
~~~~~~
|
||||
|
||||
For datetime fields, an exact second match. Allows chaining additional field
|
||||
lookups. Takes an integer between 0 and 59.
|
||||
For datetime and time fields, an exact second match. Allows chaining additional
|
||||
field lookups. Takes an integer between 0 and 59.
|
||||
|
||||
Example::
|
||||
|
||||
Event.objects.filter(timestamp__second=31)
|
||||
Event.objects.filter(time__second=2)
|
||||
Event.objects.filter(timestamp__second__gte=31)
|
||||
|
||||
SQL equivalent::
|
||||
|
||||
SELECT ... WHERE EXTRACT('second' FROM timestamp) = '31';
|
||||
SELECT ... WHERE EXTRACT('second' FROM time) = '2';
|
||||
SELECT ... WHERE EXTRACT('second' FROM timestamp) >= '31';
|
||||
|
||||
(The exact SQL syntax varies for each database engine.)
|
||||
|
||||
When :setting:`USE_TZ` is ``True``, values are converted to the current time
|
||||
zone before filtering.
|
||||
For datetime fields, when :setting:`USE_TZ` is ``True``, values are converted
|
||||
to the current time zone before filtering.
|
||||
|
||||
.. versionchanged:: 1.9
|
||||
|
||||
Added support for :class:`~django.db.models.TimeField` on SQLite (other
|
||||
databases supported it as of 1.7).
|
||||
|
||||
.. versionchanged:: 1.9
|
||||
|
||||
|
|
|
@ -245,6 +245,10 @@ Models
|
|||
(such as :lookup:`exact`, :lookup:`gt`, :lookup:`lt`, etc.). For example:
|
||||
``Entry.objects.filter(pub_date__month__gt=6)``.
|
||||
|
||||
* Time lookups (hour, minute, second) are now supported by
|
||||
:class:`~django.db.models.TimeField` for all database backends. Support for
|
||||
backends other than SQLite was added but undocumented in Django 1.7.
|
||||
|
||||
* You can specify the ``output_field`` parameter of the
|
||||
:class:`~django.db.models.Avg` aggregate in order to aggregate over
|
||||
non-numeric columns, such as ``DurationField``.
|
||||
|
@ -374,6 +378,12 @@ Database backend API
|
|||
* To use the new ``date`` lookup, third-party database backends may need to
|
||||
implement the ``DatabaseOperations.datetime_cast_date_sql()`` method.
|
||||
|
||||
* The ``DatabaseOperations.time_extract_sql()`` method was added. It calls the
|
||||
existing ``date_extract_sql()`` method. This method is overridden by the
|
||||
SQLite backend to add time lookups (hour, minute, second) to
|
||||
:class:`~django.db.models.TimeField`, and may be needed by third-party
|
||||
database backends.
|
||||
|
||||
Default settings that were tuples are now lists
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -11,6 +11,14 @@ from django.utils import six
|
|||
from django.utils.encoding import python_2_unicode_compatible
|
||||
|
||||
|
||||
class Alarm(models.Model):
|
||||
desc = models.CharField(max_length=100)
|
||||
time = models.TimeField()
|
||||
|
||||
def __str__(self):
|
||||
return '%s (%s)' % (self.time, self.desc)
|
||||
|
||||
|
||||
class Author(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from .models import Alarm
|
||||
|
||||
|
||||
class TimeFieldLookupTests(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
# Create a few Alarms
|
||||
self.al1 = Alarm.objects.create(desc='Early', time='05:30')
|
||||
self.al2 = Alarm.objects.create(desc='Late', time='10:00')
|
||||
self.al3 = Alarm.objects.create(desc='Precise', time='12:34:56')
|
||||
|
||||
def test_hour_lookups(self):
|
||||
self.assertQuerysetEqual(
|
||||
Alarm.objects.filter(time__hour=5),
|
||||
['<Alarm: 05:30:00 (Early)>'],
|
||||
ordered=False
|
||||
)
|
||||
|
||||
def test_minute_lookups(self):
|
||||
self.assertQuerysetEqual(
|
||||
Alarm.objects.filter(time__minute=30),
|
||||
['<Alarm: 05:30:00 (Early)>'],
|
||||
ordered=False
|
||||
)
|
||||
|
||||
def test_second_lookups(self):
|
||||
self.assertQuerysetEqual(
|
||||
Alarm.objects.filter(time__second=56),
|
||||
['<Alarm: 12:34:56 (Precise)>'],
|
||||
ordered=False
|
||||
)
|
Loading…
Reference in New Issue