New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixed #13312 -- Added a way to customize the order_by() of null fields. #6981
Conversation
template = None | ||
if self.nullslast: | ||
template = 'IF(ISNULL(%(expression)s),1,0),%(expression)s %(ordering)s ' | ||
sql, params = self.as_sql(compiler, connection, template=template) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason not to use the pattern used in other as_*
methods?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, my bad. Fixed
I suppose we could make |
bdee62b
to
8d84390
Compare
sql, params = self.as_sql(compiler, connection, template=template) | ||
return sql, params | ||
|
||
def as_postgresql(self, compiler, connection): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'd be better to rename this as_sql
and to remove the as_oracle
method since it is the "default" implementation that all backends follow unless they opt out. As-is, any third party backends must implement their own version even if they support NULLS LAST syntax, such as mssql.
@charettes now asc and desc are chainable with nulls_last as asked. |
@@ -879,6 +883,8 @@ def get_source_expressions(self): | |||
return [self.expression] | |||
|
|||
def as_sql(self, compiler, connection, template=None, **extra_context): | |||
if not template and self.nullslast: | |||
template = '%(expression)s %(ordering)s NULLS LAST' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use "%s NULLS LAST" % self.template
to prevent code duplication.
Should we also add support for |
I feel like making Event.objects.order_by(F('start_date').asc(nulls='first')) or
Event.objects.order_by(F('start_date').asc(nulls_first=True)) |
118136b
to
d7c4e3f
Compare
@charette now comply with the api you suggest. |
Don't like the string argument, sorry, and I feel that a tri-value would constantly require referencing the docs. I'd prefer |
I can't think of any. Maybe the this should be taken to the developpers mailing list? |
I am ok with nulls_first=True syntax but before implementing it I need a green flag of yours so just let me know the endorsed syntax and I'll implement it. |
I like the |
@boblefrag let's go with You might want to delegate |
FWIW |
d7c4e3f
to
9f85a72
Compare
@charettes nulls_first=True and nulls_last=True. What's next step? |
The code is LGTM. Maybe break the |
9f85a72
to
c6ffc79
Compare
@charettes done. |
@@ -90,6 +90,90 @@ def test_order_by_override(self): | |||
attrgetter("headline") | |||
) | |||
|
|||
def test_order_by_nulls(self): | |||
with self.assertRaises(ValueError): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use assertRaisesMessage
.
Article.objects.create( | ||
headline="Article 5", | ||
author=author_1, | ||
pub_date=datetime(2005, 7, 28) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a trailing comma.
You can reuse the |
c6ffc79
to
f47ba4d
Compare
@charettes is there anything left to be done for making this PR mergeable ? |
Marked the ticket as ready for final review. |
Documentation is missing. See our patch review checklist for details. Please uncheck "Needs documentation" on the ticket after updating. |
def __init__(self, expression, descending=False, nulls_first=False, | ||
nulls_last=False): | ||
if nulls_first and nulls_last: | ||
raise ValueError("nulls_first and nulls_last are mutualy exclusive") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor typo: should be 'mutually'.
f47ba4d
to
ad5219f
Compare
fixed typo and added doc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a test failure on Oracle also:
======================================================================
FAIL: test_order_by_nulls_first (ordering.tests.OrderingTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/tim/code/django/tests/ordering/tests.py", line 138, in test_order_by_nulls_first
attrgetter("headline")
File "/home/tim/code/django/django/test/testcases.py", line 964, in assertQuerysetEqual
return self.assertEqual(list(items), values, msg=msg)
AssertionError: Lists differ: ['Article 1', 'Article 4', 'Article 3', 'Article 2'] != ['Article 1', 'Article 2', 'Article 3', 'Article 4']
First differing element 1:
'Article 4'
'Article 2'
- ['Article 1', 'Article 4', 'Article 3', 'Article 2']
? ^ ^
+ ['Article 1', 'Article 2', 'Article 3', 'Article 4']
@@ -551,10 +551,14 @@ calling the appropriate methods on the wrapped expression. | |||
.. method:: asc() | |||
|
|||
Returns the expression ready to be sorted in ascending order. | |||
`nulls_last` and `nulls_first` define how null values |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These should be added as kwargs to the method signatures.
Use double backticks rather than single.
Add a mention in the 1.11 release notes.
Add .. versionchanged:: 1.11
annotations as described in https://docs.djangoproject.com/en/dev/internals/contributing/writing-documentation/#documenting-new-features.
@@ -90,6 +96,58 @@ def test_order_by_override(self): | |||
attrgetter("headline") | |||
) | |||
|
|||
def test_order_by_nulls_first_and_last(self): | |||
with self.assertRaisesMessage(ValueError, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use:
msg = '...'
with self.assertRaisesMessage(ValueError, msg):
Article.objects.filter(headline="Article 3").update(author=self.author_1) | ||
Article.objects.filter(headline="Article 4").update(author=self.author_2) | ||
|
||
# check that asc and desc are chainable with nulls_last |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
chop "check that" and just state the expected behavior.
|
||
# check that asc and desc are chainable with nulls_last | ||
|
||
self.assertQuerysetEqual( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use:
self.assertSequenceEqual(
Article.objects.order_by(F("author").desc(nulls_last=True)),
[self.a4, ...]
)
def setUp(self): | ||
self.a1 = Article.objects.create( | ||
|
||
@classmethod |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please make this change and the test changes unrelated to the bug fix in a separate commit for clarity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this have been made to comply with #6981 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change is fine, I'm just asking that it be a be separate commit similar to 31098e3 so that the refactoring isn't mixed in with the new feature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense?
1a57da5
to
7f2db39
Compare
7f2db39
to
520c517
Compare
520c517
to
48fb868
Compare
@timgraham here is the 2 commits. Let me know if it's ok for you. |
@@ -213,14 +265,10 @@ def test_order_by_pk(self): | |||
Ensure that 'pk' works as an ordering option in Meta. | |||
Refs #8291. | |||
""" | |||
Author.objects.create(pk=1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My think was that the setUpTestData
changes would be the first commit and changes like this would be included there so that the bug fix commit doesn't have to make changes in unrelated tests.
] | ||
) | ||
|
||
def test_order_by_nulls_first(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add authors for article3
and article4
like in test_order_by_nulls_last
:
--- a/tests/ordering/tests.py
+++ b/tests/ordering/tests.py
@@ -125,6 +125,8 @@ class OrderingTests(TestCase):
)
def test_order_by_nulls_first(self):
+ Article.objects.filter(headline="Article 3").update(author=self.author_1)
+ Article.objects.filter(headline="Article 4").update(author=self.author_2)
self.assertSequenceEqual(
Article.objects.order_by(F("author").asc(nulls_first=True)), [
self.a1,
@@ -138,8 +140,8 @@ class OrderingTests(TestCase):
Article.objects.order_by(F("author").desc(nulls_first=True)), [
self.a1,
self.a2,
- self.a3,
- self.a4
+ self.a4,
+ self.a3
]
)
Now we have 4 articles without authors, hence we do not check that asc
and desc
works with nulls_first
flag.
Updated in #7563. |
https://code.djangoproject.com/ticket/13312