From abaa3ed4bd0c010c30aa338713a56b5c2cb45cf7 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 4 Nov 2010 16:03:05 +0000 Subject: [PATCH] Fixed #13935, added support for using QuerySet.dates across related fields. Thanks to valyagolev for his work on the patch. git-svn-id: http://code.djangoproject.com/svn/django/trunk@14461 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/query.py | 10 +-- django/db/models/sql/subqueries.py | 17 +++++- tests/regressiontests/dates/__init__.py | 0 tests/regressiontests/dates/models.py | 23 +++++++ tests/regressiontests/dates/tests.py | 81 +++++++++++++++++++++++++ 5 files changed, 121 insertions(+), 10 deletions(-) create mode 100644 tests/regressiontests/dates/__init__.py create mode 100644 tests/regressiontests/dates/models.py create mode 100644 tests/regressiontests/dates/tests.py diff --git a/django/db/models/query.py b/django/db/models/query.py index 7e91b0fc939..bb62f22f8ae 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -7,7 +7,8 @@ from itertools import izip from django.db import connections, router, transaction, IntegrityError from django.db.models.aggregates import Aggregate from django.db.models.fields import DateField -from django.db.models.query_utils import Q, select_related_descend, CollectedObjects, CyclicDependency, deferred_class_factory, InvalidQuery +from django.db.models.query_utils import (Q, select_related_descend, + CollectedObjects, CyclicDependency, deferred_class_factory, InvalidQuery) from django.db.models import signals, sql from django.utils.copycompat import deepcopy @@ -998,12 +999,7 @@ class DateQuerySet(QuerySet): self.query.clear_deferred_loading() self.query = self.query.clone(klass=sql.DateQuery, setup=True) self.query.select = [] - field = self.model._meta.get_field(self._field_name, many_to_many=False) - assert isinstance(field, DateField), "%r isn't a DateField." \ - % field.name - self.query.add_date_select(field, self._kind, self._order) - if field.null: - self.query.add_filter(('%s__isnull' % field.name, False)) + self.query.add_date_select(self._field_name, self._kind, self._order) def _clone(self, klass=None, setup=False, **kwargs): c = super(DateQuerySet, self)._clone(klass, False, **kwargs) diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py index a066dfeca8c..932a4429ec6 100644 --- a/django/db/models/sql/subqueries.py +++ b/django/db/models/sql/subqueries.py @@ -4,6 +4,7 @@ Query subclasses which provide extra functionality beyond simple data retrieval. from django.core.exceptions import FieldError from django.db import connections +from django.db.models.fields import DateField from django.db.models.sql.constants import * from django.db.models.sql.datastructures import Date from django.db.models.sql.expressions import SQLEvaluator @@ -184,12 +185,19 @@ class DateQuery(Query): compiler = 'SQLDateCompiler' - def add_date_select(self, field, lookup_type, order='ASC'): + def add_date_select(self, field_name, lookup_type, order='ASC'): """ Converts the query into a date extraction query. """ - result = self.setup_joins([field.name], self.get_meta(), - self.get_initial_alias(), False) + result = self.setup_joins( + field_name.split(LOOKUP_SEP), + self.get_meta(), + self.get_initial_alias(), + False + ) + field = result[0] + assert isinstance(field, DateField), "%r isn't a DateField." \ + % field.name alias = result[3][-1] select = Date((alias, field.column), lookup_type) self.select = [select] @@ -199,6 +207,9 @@ class DateQuery(Query): self.distinct = True self.order_by = order == 'ASC' and [1] or [-1] + if field.null: + self.add_filter(("%s__isnull" % field_name, False)) + class AggregateQuery(Query): """ An AggregateQuery takes another query as a parameter to the FROM diff --git a/tests/regressiontests/dates/__init__.py b/tests/regressiontests/dates/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regressiontests/dates/models.py b/tests/regressiontests/dates/models.py new file mode 100644 index 00000000000..e1fc1e74fe6 --- /dev/null +++ b/tests/regressiontests/dates/models.py @@ -0,0 +1,23 @@ +from django.db import models + + +class Article(models.Model): + title = models.CharField(max_length=100) + pub_date = models.DateField() + + categories = models.ManyToManyField("Category", related_name="articles") + + def __unicode__(self): + return self.title + +class Comment(models.Model): + article = models.ForeignKey(Article, related_name="comments") + text = models.TextField() + pub_date = models.DateField() + approval_date = models.DateField(null=True) + + def __unicode__(self): + return 'Comment to %s (%s)' % (self.article.title, self.pub_date) + +class Category(models.Model): + name = models.CharField(max_length=255) diff --git a/tests/regressiontests/dates/tests.py b/tests/regressiontests/dates/tests.py new file mode 100644 index 00000000000..769d0742152 --- /dev/null +++ b/tests/regressiontests/dates/tests.py @@ -0,0 +1,81 @@ +from datetime import datetime + +from django.test import TestCase + +from models import Article, Comment, Category + + +class DatesTests(TestCase): + def test_related_model_traverse(self): + a1 = Article.objects.create( + title="First one", + pub_date=datetime(2005, 7, 28), + ) + a2 = Article.objects.create( + title="Another one", + pub_date=datetime(2010, 7, 28), + ) + a3 = Article.objects.create( + title="Third one, in the first day", + pub_date=datetime(2005, 7, 28), + ) + + a1.comments.create( + text="Im the HULK!", + pub_date=datetime(2005, 7, 28), + ) + a1.comments.create( + text="HULK SMASH!", + pub_date=datetime(2005, 7, 29), + ) + a2.comments.create( + text="LMAO", + pub_date=datetime(2010, 7, 28), + ) + a3.comments.create( + text="+1", + pub_date=datetime(2005, 8, 29), + ) + + c = Category.objects.create(name="serious-news") + c.articles.add(a1, a3) + + self.assertQuerysetEqual( + Comment.objects.dates("article__pub_date", "year"), [ + datetime(2005, 1, 1), + datetime(2010, 1, 1), + ], + lambda d: d, + ) + self.assertQuerysetEqual( + Comment.objects.dates("article__pub_date", "month"), [ + datetime(2005, 7, 1), + datetime(2010, 7, 1), + ], + lambda d: d + ) + self.assertQuerysetEqual( + Comment.objects.dates("article__pub_date", "day"), [ + datetime(2005, 7, 28), + datetime(2010, 7, 28), + ], + lambda d: d + ) + self.assertQuerysetEqual( + Article.objects.dates("comments__pub_date", "day"), [ + datetime(2005, 7, 28), + datetime(2005, 7, 29), + datetime(2005, 8, 29), + datetime(2010, 7, 28), + ], + lambda d: d + ) + self.assertQuerysetEqual( + Article.objects.dates("comments__approval_date", "day"), [] + ) + self.assertQuerysetEqual( + Category.objects.dates("articles__pub_date", "day"), [ + datetime(2005, 7, 28), + ], + lambda d: d, + )