From addd3df3bd39730cd82c52d9726c9b7dbf1bdb8f Mon Sep 17 00:00:00 2001 From: Karen Tracey Date: Sun, 8 Feb 2009 05:08:06 +0000 Subject: [PATCH] Fixed #7672 -- Added a 'week_day' lookup type. Many thanks to Ross Poulton for the proposal and implementation on all built-in database backends.. git-svn-id: http://code.djangoproject.com/svn/django/trunk@9818 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 3 ++- django/db/backends/mysql/base.py | 7 ++++++- django/db/backends/oracle/base.py | 12 +++++++++--- django/db/backends/postgresql/operations.py | 7 ++++++- django/db/backends/sqlite3/base.py | 5 ++++- django/db/models/fields/__init__.py | 8 ++++---- django/db/models/sql/constants.py | 2 +- django/db/models/sql/where.py | 6 +++--- docs/ref/models/querysets.txt | 21 +++++++++++++++++++++ tests/modeltests/basic/models.py | 12 ++++++++++++ 10 files changed, 68 insertions(+), 15 deletions(-) diff --git a/AUTHORS b/AUTHORS index af45060b36..210f52c5a5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -318,9 +318,10 @@ answer newbie questions, and generally made Django that much better: Michael Placentra II Luke Plant plisk - Mihai Preda Daniel Poelzleithner polpak@yahoo.com + Ross Poulton + Mihai Preda Matthias Pronk Jyrki Pulliainen Thejaswi Puthraya diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index bb3e129779..00da726ac5 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -116,7 +116,12 @@ class DatabaseFeatures(BaseDatabaseFeatures): class DatabaseOperations(BaseDatabaseOperations): def date_extract_sql(self, lookup_type, field_name): # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html - return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name) + if lookup_type == 'week_day': + # DAYOFWEEK() returns an integer, 1-7, Sunday=1. + # Note: WEEKDAY() returns 0-6, Monday=0. + return "DAYOFWEEK(%s)" % field_name + else: + return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name) def date_trunc_sql(self, lookup_type, field_name): fields = ['year', 'month', 'day', 'hour', 'minute', 'second'] diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 38f51a707f..d1930e4ffd 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -72,7 +72,11 @@ WHEN (new.%(col_name)s IS NULL) def date_extract_sql(self, lookup_type, field_name): # http://download-east.oracle.com/docs/cd/B10501_01/server.920/a96540/functions42a.htm#1017163 - return "EXTRACT(%s FROM %s)" % (lookup_type, field_name) + if lookup_type == 'week_day': + # TO_CHAR(field, 'D') returns an integer from 1-7, where 1=Sunday. + return "TO_CHAR(%s, 'D')" % field_name + else: + return "EXTRACT(%s FROM %s)" % (lookup_type, field_name) def date_trunc_sql(self, lookup_type, field_name): # Oracle uses TRUNC() for both dates and numbers. @@ -268,9 +272,11 @@ class DatabaseWrapper(BaseDatabaseWrapper): self.connection = Database.connect(conn_string, **self.options) cursor = FormatStylePlaceholderCursor(self.connection) # Set oracle date to ansi date format. This only needs to execute - # once when we create a new connection. + # once when we create a new connection. We also set the Territory + # to 'AMERICA' which forces Sunday to evaluate to a '1' in TO_CHAR(). cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS' " - "NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'") + "NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF' " + "NLS_TERRITORY = 'AMERICA'") try: self.oracle_version = int(self.connection.version.split('.')[0]) # There's no way for the DatabaseOperations class to know the diff --git a/django/db/backends/postgresql/operations.py b/django/db/backends/postgresql/operations.py index 9f221b3822..45b17b5778 100644 --- a/django/db/backends/postgresql/operations.py +++ b/django/db/backends/postgresql/operations.py @@ -26,7 +26,12 @@ class DatabaseOperations(BaseDatabaseOperations): def date_extract_sql(self, lookup_type, field_name): # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT - return "EXTRACT('%s' FROM %s)" % (lookup_type, field_name) + if lookup_type == 'week_day': + # Using EXTRACT(), PostgreSQL days are indexed as Sunday=0, Saturday=6. + # If we instead us TO_CHAR, they're indexed with Sunday=1, Saturday=7 + return "TO_CHAR(%s, 'D')" % field_name + else: + return "EXTRACT('%s' FROM %s)" % (lookup_type, field_name) def date_trunc_sql(self, lookup_type, field_name): # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 071d421b58..ba0ef16b61 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -207,7 +207,10 @@ def _sqlite_extract(lookup_type, dt): dt = util.typecast_timestamp(dt) except (ValueError, TypeError): return None - return unicode(getattr(dt, lookup_type)) + if lookup_type == 'week_day': + return unicode((dt.isoweekday() % 7) + 1) + else: + return unicode(getattr(dt, lookup_type)) def _sqlite_date_trunc(lookup_type, dt): try: diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 274b5b4327..41e8c06b80 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -201,7 +201,7 @@ class Field(object): sql, params = value.as_sql() return QueryWrapper(('(%s)' % sql), params) - if lookup_type in ('regex', 'iregex', 'month', 'day', 'search'): + if lookup_type in ('regex', 'iregex', 'month', 'day', 'week_day', 'search'): return [value] elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte'): return [self.get_db_prep_value(value)] @@ -490,9 +490,9 @@ class DateField(Field): curry(cls._get_next_or_previous_by_FIELD, field=self, is_next=False)) def get_db_prep_lookup(self, lookup_type, value): - # For "__month" and "__day" lookups, convert the value to a string so - # the database backend always sees a consistent type. - if lookup_type in ('month', 'day'): + # For "__month", "__day", and "__week_day" lookups, convert the value + # to a string so the database backend always sees a consistent type. + if lookup_type in ('month', 'day', 'week_day'): return [force_unicode(value)] return super(DateField, self).get_db_prep_lookup(lookup_type, value) diff --git a/django/db/models/sql/constants.py b/django/db/models/sql/constants.py index e14816c965..63c704fea1 100644 --- a/django/db/models/sql/constants.py +++ b/django/db/models/sql/constants.py @@ -4,7 +4,7 @@ import re QUERY_TERMS = dict([(x, None) for x in ( 'exact', 'iexact', 'contains', 'icontains', 'gt', 'gte', 'lt', 'lte', 'in', 'startswith', 'istartswith', 'endswith', 'iendswith', 'range', 'year', - 'month', 'day', 'isnull', 'search', 'regex', 'iregex', + 'month', 'day', 'week_day', 'isnull', 'search', 'regex', 'iregex', )]) # Size of each "chunk" for get_iterator calls. diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py index 8724906a8c..d97112e9f3 100644 --- a/django/db/models/sql/where.py +++ b/django/db/models/sql/where.py @@ -174,9 +174,9 @@ class WhereNode(tree.Node): params) elif lookup_type in ('range', 'year'): return ('%s BETWEEN %%s and %%s' % field_sql, params) - elif lookup_type in ('month', 'day'): - return ('%s = %%s' % connection.ops.date_extract_sql(lookup_type, - field_sql), params) + elif lookup_type in ('month', 'day', 'week_day'): + return ('%s = %%s' % connection.ops.date_extract_sql(lookup_type, field_sql), + params) elif lookup_type == 'isnull': return ('%s IS %sNULL' % (field_sql, (not value_annot and 'NOT ' or '')), ()) diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 1e32552570..d8d01467b9 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -1344,6 +1344,27 @@ SQL equivalent:: Note this will match any record with a pub_date on the third day of the month, such as January 3, July 3, etc. +week_day +~~~~~~~~ + +.. versionadded:: 1.1 + +For date/datetime fields, a 'day of the week' match. + +Example:: + + Entry.objects.filter(pub_date__week_day=2) + +SQL equivalent:: + + SELECT ... WHERE EXTRACT('dow' FROM pub_date) = '2'; + +(The exact SQL syntax varies for each database engine.) + +Note this will match any record with a pub_date that falls on a Monday (day 2 +of the week), regardless of the month or year in which it occurs. Week days +are indexed with day 1 being Sunday and day 7 being Saturday. + isnull ~~~~~~ diff --git a/tests/modeltests/basic/models.py b/tests/modeltests/basic/models.py index 835c5c90cf..2dbde58256 100644 --- a/tests/modeltests/basic/models.py +++ b/tests/modeltests/basic/models.py @@ -74,6 +74,8 @@ datetime.datetime(2005, 7, 28, 0, 0) >>> Article.objects.get(pub_date__year=2005, pub_date__month=7, pub_date__day=28) +>>> Article.objects.get(pub_date__week_day=5) + # The "__exact" lookup type can be omitted, as a shortcut. >>> Article.objects.get(id=1) @@ -88,6 +90,11 @@ datetime.datetime(2005, 7, 28, 0, 0) >>> Article.objects.filter(pub_date__year=2005, pub_date__month=7) [] +>>> Article.objects.filter(pub_date__week_day=5) +[] +>>> Article.objects.filter(pub_date__week_day=6) +[] + # Django raises an Article.DoesNotExist exception for get() if the parameters # don't match any object. >>> Article.objects.get(id__exact=2) @@ -100,6 +107,11 @@ Traceback (most recent call last): ... DoesNotExist: Article matching query does not exist. +>>> Article.objects.get(pub_date__week_day=6) +Traceback (most recent call last): + ... +DoesNotExist: Article matching query does not exist. + # Lookup by a primary key is the most common case, so Django provides a # shortcut for primary-key exact lookups. # The following is identical to articles.get(id=1).