I can't quite picture how operator overloading would look like, could you give an example?
Instead of this:
self.filter(end__gt=self._midnight(today))
You could write a "Field" class that implements __getattr__ and __gt__ so you could do
self.filter(Field.end > self._midnight(today))
The "Field.end > self._midnight(today)" would evaluate to an object that would just store "my field name is end and my value needs to be larger than xyz".
filter() can then look into its argument list and construct the filter criteria from the passed Field objects instead of the key value pairs as it does now.
self.filter(end__gt=self._midnight(today))
will evaluate to: self.filter(end__gt=<some_datetime_object>)
While self.filter(Field.end > self._midnight(today))
will evaluate to: self.filter(<True/False>) from datetime import datetime
class Filter():
def __init__(self, name):
self.name = name
def __gt__(self, value):
return {
"field": self.name,
"operator": ">",
"value": value
}
class FieldMeta(type):
def __getattr__(cls, name):
return Filter(name)
class Field(metaclass=FieldMeta):
pass
print(Field.end > datetime(2024, 1, 1))
This gives: {'field': 'end', 'operator': '>', 'value': datetime.datetime(2024, 1, 1, 0, 0)}
You can make python return arbitrary values for comparisons by overriding __gt__ (and lt, eq) on the first operand (which we control here since it is a Field class), it doesn't have to be a bool.Edit:
You can even make a little adapter to use this with the current filter system if you really want to:
from datetime import datetime
class Filter():
def __init__(self, name):
self.name = name
def __gt__(self, value):
return {
"field": self.name,
"operator": "gt",
"value": value
}
def __lt__(self, value):
return {
"field": self.name,
"operator": "lt",
"value": value
}
def __eq__(self, value):
return {
"field": self.name,
"operator": "eq",
"value": value
}
class FieldMeta(type):
def __getattr__(cls, name):
return Filter(name)
class Field(metaclass=FieldMeta):
pass
def _(*args):
kwargs = {}
for arg in args:
k = arg["field"] + "__" + arg["operator"]
kwargs[k] = arg["value"]
return kwargs
def filter(**kwargs):
for k, v in kwargs.items():
print(f"{k} = {v}")
filter(**_(Field.end > datetime(2024, 1, 1)))
This printsend__gt = 2024-01-01 00:00:00
Also, apparently SQLAlchemy does exactly what I proposed so apparently they are erring in their ways too.
I honestly don’t find it that bad.
And despite my misgivings, it’s really ergonomic.
You don't need to return a boolean. You need to return an object that implements __bool__.
Get it ?
And even then, when you're dealing with objects that are special to expressions, you don't actually even need __bool__ that much
def __gt__(self, value):
return Q(**{ f"{self.name}__gt": value })
and your original code should work as-is without the need for _() self.filter(Field.end > self._midnight(today))
https://docs.djangoproject.com/en/6.0/topics/db/queries/#com... admin/event/?end__gt=2026-07-26T12:00:00
Being able to do ad-hoc queries using the same paradigm your app queries are written in, and then pass urls around with those queries included (e.g., quick one-off reports or answers to client questions) is so helpful.If you mean “would it allow arbitrary queries from normal view code”, then no it would not—not unless you string eval on the backend or something else dangerous.
https://docs.peewee-orm.com/en/latest/peewee/querying.html#f...