From 4c4209b14449554382b541d709749340f9b1a7ae Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Wed, 14 Feb 2007 06:32:32 +0000 Subject: [PATCH] Changed __year lookup to use a BETWEEN SQL statement instead of comparing the result of EXTRACT(year). This should be more efficient. git-svn-id: http://code.djangoproject.com/svn/django/trunk@4505 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/fields/__init__.py | 10 ++++++++-- django/db/models/query.py | 26 +++++++++++++------------- tests/modeltests/basic/models.py | 10 ++++++++-- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 024fa95b8e..0f8a1cdee2 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -164,7 +164,7 @@ class Field(object): def get_db_prep_lookup(self, lookup_type, value): "Returns field's value prepared for database lookup." - if lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte', 'year', 'month', 'day', 'search'): + if lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte', 'month', 'day', 'search'): return [value] elif lookup_type in ('range', 'in'): return value @@ -178,7 +178,13 @@ class Field(object): return ["%%%s" % prep_for_like_query(value)] elif lookup_type == 'isnull': return [] - raise TypeError, "Field has invalid lookup: %s" % lookup_type + elif lookup_type == 'year': + try: + value = int(value) + except ValueError: + raise ValueError("The __year lookup type requires an integer argument") + return ['%s-01-01 00:00:00' % value, '%s-12-31 23:59:59.999999' % value] + raise TypeError("Field has invalid lookup: %s" % lookup_type) def has_default(self): "Returns a boolean of whether this field has a default value." diff --git a/django/db/models/query.py b/django/db/models/query.py index 8a784be7c8..2209521d93 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -198,17 +198,17 @@ class QuerySet(object): counter = self._clone() counter._order_by = () counter._select_related = False - + offset = counter._offset limit = counter._limit counter._offset = None counter._limit = None - + try: select, sql, params = counter._get_sql_clause() except EmptyResultSet: return 0 - + cursor = connection.cursor() if self._distinct: id_col = "%s.%s" % (backend.quote_name(self.model._meta.db_table), @@ -553,7 +553,7 @@ class ValuesQuerySet(QuerySet): else: # Default to all fields. columns = [f.column for f in self.model._meta.fields] field_names = [f.attname for f in self.model._meta.fields] - + select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns] cursor = connection.cursor() cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) @@ -576,12 +576,12 @@ class DateQuerySet(QuerySet): if self._field.null: self._where.append('%s.%s IS NOT NULL' % \ (backend.quote_name(self.model._meta.db_table), backend.quote_name(self._field.column))) - + try: select, sql, params = self._get_sql_clause() except EmptyResultSet: raise StopIteration - + sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1 %s' % \ (backend.get_date_trunc_sql(self._kind, '%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(self._field.column))), sql, self._order) @@ -598,15 +598,15 @@ class DateQuerySet(QuerySet): c._kind = self._kind c._order = self._order return c - + class EmptyQuerySet(QuerySet): def __init__(self, model=None): super(EmptyQuerySet, self).__init__(model) self._result_cache = [] - + def count(self): return 0 - + def delete(self): pass @@ -708,9 +708,9 @@ def get_where_clause(lookup_type, table_prefix, field_name, value): return '%s%s IN (%s)' % (table_prefix, field_name, in_string) else: raise EmptyResultSet - elif lookup_type == 'range': + elif lookup_type in ('range', 'year'): return '%s%s BETWEEN %%s AND %%s' % (table_prefix, field_name) - elif lookup_type in ('year', 'month', 'day'): + elif lookup_type in ('month', 'day'): return "%s = %%s" % backend.get_date_extract_sql(lookup_type, table_prefix + field_name) elif lookup_type == 'isnull': return "%s%s IS %sNULL" % (table_prefix, field_name, (not value and 'NOT ' or '')) @@ -791,7 +791,7 @@ def parse_lookup(kwarg_items, opts): if len(path) < 1: raise TypeError, "Cannot parse keyword query %r" % kwarg - + if value is None: # Interpret '__exact=None' as the sql '= NULL'; otherwise, reject # all uses of None as a query value. @@ -1007,7 +1007,7 @@ def delete_objects(seen_objs): pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]) for f in cls._meta.many_to_many: if isinstance(f, GenericRelation): - from django.contrib.contenttypes.models import ContentType + from django.contrib.contenttypes.models import ContentType query_extra = 'AND %s=%%s' % f.rel.to._meta.get_field(f.content_type_field_name).column args_extra = [ContentType.objects.get_for_model(cls).id] else: diff --git a/tests/modeltests/basic/models.py b/tests/modeltests/basic/models.py index 1663068892..9af13c0e3e 100644 --- a/tests/modeltests/basic/models.py +++ b/tests/modeltests/basic/models.py @@ -12,7 +12,7 @@ class Article(models.Model): class Meta: ordering = ('pub_date','headline') - + def __str__(self): return self.headline @@ -319,7 +319,6 @@ AttributeError: Manager isn't accessible via Article instances >>> Article.objects.filter(id__lte=4).delete() >>> Article.objects.all() [, , , ] - """} from django.conf import settings @@ -358,4 +357,11 @@ __test__['API_TESTS'] += """ >>> a10 = Article.objects.create(headline="Article 10", pub_date=datetime(2005, 7, 31, 12, 30, 45)) >>> Article.objects.get(headline="Article 10") + +# Edge-case test: A year lookup should retrieve all objects in the given +year, including Jan. 1 and Dec. 31. +>>> a11 = Article.objects.create(headline='Article 11', pub_date=datetime(2008, 1, 1)) +>>> a12 = Article.objects.create(headline='Article 12', pub_date=datetime(2008, 12, 31, 23, 59, 59, 999999)) +>>> Article.objects.filter(pub_date__year=2008) +[, ] """