-
Notifications
You must be signed in to change notification settings - Fork 5
Add easy way to annotate Route QS with "level" #131
Conversation
This allows for |
df82e44
to
31745dc
Compare
I might as well add a |
conman/routes/managers.py
Outdated
""" | ||
Annotate the queryset with the level of each item. | ||
|
||
The level reflects the number of forward slashes in the path. |
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 level is one-indexed; it reflects the number of forward slashes in the path.
tests/routes/test_expressions.py
Outdated
expected = ( | ||
'SELECT "routes_route"."id", ' + | ||
'CHAR_LENGTH("routes_route"."url") - ' + | ||
'CHAR_LENGTH(REPLACE("routes_route"."url", \'/\', \'\')) AS "level" ' + |
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.
Perhaps use a triple quoted string to avoid the need to escape any quotes.
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.
Good plan, thanks.
tests/routes/test_expressions.py
Outdated
CharCount('url', char='no') | ||
|
||
with self.assertRaisesMessage(ValueError, msg): | ||
CharCount('url', char='') |
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.
Perhaps this is a place to use subTest
.
tests/routes/test_expressions.py
Outdated
def test_calling_format(self): | ||
"""Ensure the 'char' argument is always a keyword-arg.""" | ||
with self.assertRaises(TypeError): | ||
CharCount('url', 'unacceptable') |
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 might be cleaner to use a single character here. so we're violating only one assumption of the api.
tests/routes/test_managers.py
Outdated
result = Route.objects.with_level().order_by('level') | ||
self.assertEqual(result[0].level, 1) | ||
self.assertEqual(result[1].level, 2) | ||
self.assertEqual(result[2].level, 3) |
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.
Perhaps:
self.assertSequenceEqual(result.values_list('level', flat=True), [1, 2, 3])
tests/routes/test_expressions.py
Outdated
'CHAR_LENGTH(REPLACE("routes_route"."url", \'/\', \'\')) AS "level" ' + | ||
'FROM "routes_route"' | ||
) | ||
self.assertEqual(str(qs.query), expected) |
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.
str(qs.query)
isn't perfect, since it does the interpolation of any params in python, not in sql. It might be worth using connection.queries
instead.
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.
Thanks for the heads up -- I'll give that a shot.
31745dc
to
319d6c8
Compare
@Ian-Foote thanks for the review. I've rebased with your (slightly altered) changes, and added a How does that look? |
319d6c8
to
9a9c0da
Compare
@level.setter | ||
def level(self, new_value): | ||
"""Silently fails to allow queryset annotation to work.""" | ||
pass |
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.
Are you sure the annotation is used instead of the property if it exists? Or does that not matter?
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's not that the annotation is used, exactly, it's that when an annotated object is instantiated, the attribute is set. Without this, and with the @property
, a "can't set attribute" error is raised.
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.
I see - the annotation is used for sql-level operations like filtering, but the descriptor whenever we're in python.
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.
Exactly :)
conman/routes/managers.py
Outdated
|
||
def with_level(self, level=None): | ||
""" | ||
Annotate the queryset with the (1-indexed) level of each item. |
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.
I see why you chose 1-indexed, but I'm not sure that's the best api. It seems more natural to me to care about how many parts the url has rather than the number of /
separators.
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.
Yeah, I wasn't sure about it myself. I went for this simply because the implementation was marginally easier -- you'll probably not be the last person to think it's strange -- I'll look into changing it.
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.
conman/routes/managers.py
Outdated
""" | ||
Annotate the queryset with the (1-indexed) level of each item. | ||
|
||
The level reflects the number of forward slashes in the path. |
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.
Do we guarantee elsewhere that a url
always starts and ends with a /
?
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.
Yes, it's a strange necessity of conman -- it makes other similar operations easy.
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.
On the whole, it shouldn't cause too many issues, I hope.
Thanks |
In order to create a navigation on a test project, I have had to add the following code:
I think it would be nice to have this level-annotation functionality built into
RouteManager
as an optional addition. Something likeRoute.objects.with_levels().filter(level=2)
strikes me as nicer.Update. Okay, I've made this a PR now. It doesn't have exactly the same interface as above. See comment below for new syntax.