Let's assume there is a line of code to perform a query using the Django ORM that contains a very long 'lookup name':
QuerySet.filter(myfk__child__onetoone__another__manytomany__relation__monster__relationship__mycustomlookup=':P')
I'd like to break the line to follow pep8, specially 79 characters limit
I know we can do something like:
QuerySet.filter(
**{
'myfk__child__onetoone__another'
'__manytomany__relation__monster'
'__relationship__mycustomlookup': ':P'
}
)
But I'm wondering if there is another, maybe more pythonic/accepted, way ?
Maybe using LOOKUP_SEP
to join the lookup names is a bit more paletable?
from django.db.models.constants import LOOKUP_SEP
lookup = LOOKUP_SEP.join(['myfk', 'child', 'onetoone', 'another', 'manytomany',
'relation', 'monster', 'relationship',
'mycustomlookup'])
QuerySet.filter(**{lookup:':P'})
pep8 says:
The preferred way of wrapping long lines is by using Python's implied line continuation inside parentheses, brackets and braces.
That's what you've done, so I think what you've got is the most pythonic (or, at least, most pep8ic) way of doing it.
EDIT (A simplified more attractive answer is here. The original detailed answer is below under line.)
I wrote a module django_dot_filter.py that helps to write query filters more naturally and readable. An expression starts with symbol V
and names separated by dots:
from django_dot_filter import V
QuerySet.filter(V.myfk.child.onetoone.another.manytomany
.relation.monster.relationship
.mycustomlookup == ':P')
I read it like "this unknown Variable" with fields... therefore I use the letter V
. That class is really only a symbol that can be followed by dots, methods, operators etc., everything separated by .
instead of __
.
Standard readable relationship operators like <
, <=
, ==
or !=
are supported, also parentheses and boolean operators &
, |
, ~
.
Queryset.filter((V.some_related.my_field >= 10)
| ~V.field_x.startswith('Y') & (V.date_field.year() == 2017)
& V.price.range(10, 100))
Every lookup can be written the classic way like an attribute V.date_field.year == 2017
or like a method V.date_field.year() == 2017
. Many lookups are much more readable as a method with an argument V.my_field.regex(r'^[abc]')
instead of my_field__regex=value
. It is much more readable for me to see a convention that .date()
is a lookup method, but .date
is a field.
It is no magic. Only a method with arguments or a relationship operator is every times the last part of lookup. A method without arguments is only a symbol that it is a lookup. Something with a value follows always. Expressions are compiled to Q expressions, including boolean expressions. They can be easily reused in similar projects, saved to variables etc., while exclude(..)
conditions instead of missing !=
operator are less reusable.
(No unsupported features are currently known. Some tests have been written. If I get enough feedback, it can a package. It is a little more verbose than the classic good name=value
, suitable for simple cases.
A different answer, if you like readable filters with possible long chains of related fields even if they are complicated.
I wrote a simple module django_dot_filter.py today, that allows to use dot syntax for fields on related models and use operators ==, !=, <, <=, >, >= for conditions. It can use bitwise operators ~ | & as boolean operators similarly like Q objects use, but due to operators priority, the comparision must be enclosed in parentheses. It is inspired by syntax used in SQLAlchemy and Pandas.
doc string:
class V(...):
"""
Syntax suger for more readable queryset filters with "." instead "__"
The name "V" can be understand like "variable", because a shortcut for
"field" is occupied yet.
The syntax is very similar to SQLAlchemy or Pandas.
Operators < <= == != >= > are supperted in filters.
>>> from django_dot_filter import V
>>>
>>> qs = Product.objects.filter(V.category.name == 'books',
>>> V.name >= 'B', V.name < 'F',
>>> (V.price < 15) | (V.date_created != today),
>>> ~V.option.in_(['ABC', 'XYZ'])
>>> )
This is the same as
>>> qs = Product.objects.filter(category__name='books',
>>> name__gte='B', name__lt='F',
>>> Q(price__lt=15) | ~Q(date_created=today),
>>> ~Q(option__in=['ABC', 'XYZ'])
>>> )
"""
(The class "V" automatically creates a new instance if used with dot. All elements are compiled to Q expression after relational operator or after .in_(iterable)
method and the instance is deleted again.)
Some examples from tests
# this is V. syntax compiled Q syntax
test_eq(V.a.b.c == 1, Q(a__b__c=1))
test_eq(V.a == 1, Q(a=1))
test_eq(V.a != 1, ~Q(a=1))
test_eq(V.a < 2, Q(a__lt=2))
test_eq(V.a <= 3, Q(a__lte=3))
test_eq(V.a > 'abc', Q(a__gt='abc'))
test_eq(V.a >= 3.14, Q(a__gte=3.14))
test_eq((V.a == 1) & (V.b == 2), Q(a=1) & Q(b=2))
test_eq((V.a == 1) | (V.b == 2), Q(a=1) | Q(b=2))
test_eq((V.a == 1) | ~(V.b == 2), Q(a=1) | ~Q(b=2))
# method "in_(..)" is used because the word "in" is reserved.
test_eq(V.first_name.in_([1, 2]), Q(first_name__in=[1, 2]))
test_eq(~V.a.in_(('Tim', 'Joe')), ~Q(a__in=('Tim', 'Joe')))
# this should be eventually improved to support all lookup
# functions automatically e.g. by ".contains('abc')" instead of "=="
test_eq(V.a.contains == 'abc', Q(a__contains='abc'))
It's a little joke inspired by your question, but it works. I remember some old discussion of (core developers? vague memories) that the syntax name__operator=value
wouldn't be used again if Django be a new project. It is very concise, but less readable. It is too late to have two official syntaxes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With