wip adding filtering and order by with field chain access instead of strings
This commit is contained in:
@ -75,7 +75,7 @@ class UndefinedType: # pragma no cover
|
||||
|
||||
Undefined = UndefinedType()
|
||||
|
||||
__version__ = "0.10.3"
|
||||
__version__ = "0.10.4"
|
||||
__all__ = [
|
||||
"Integer",
|
||||
"BigInteger",
|
||||
|
||||
@ -39,7 +39,7 @@ from ormar.models.helpers import (
|
||||
sqlalchemy_columns_from_model_fields,
|
||||
)
|
||||
from ormar.models.quick_access_views import quick_access_set
|
||||
from ormar.queryset import QuerySet
|
||||
from ormar.queryset import FieldAccessor, QuerySet
|
||||
from ormar.relations.alias_manager import AliasManager
|
||||
from ormar.signals import Signal, SignalEmitter
|
||||
|
||||
@ -561,3 +561,14 @@ class ModelMetaclass(pydantic.main.ModelMetaclass):
|
||||
f"need to call update_forward_refs()."
|
||||
)
|
||||
return QuerySet(model_cls=cls)
|
||||
|
||||
def __getattr__(self, item: str) -> Any:
|
||||
if item in object.__getattribute__(self, "Meta").model_fields:
|
||||
field = self.Meta.model_fields.get(item)
|
||||
if field.is_relation:
|
||||
return FieldAccessor(
|
||||
source_model=self, model=field.to, access_chain=item
|
||||
)
|
||||
else:
|
||||
return FieldAccessor(source_model=self, field=field, access_chain=item)
|
||||
return object.__getattribute__(self, item)
|
||||
|
||||
@ -3,6 +3,7 @@ Contains QuerySet and different Query classes to allow for constructing of sql q
|
||||
"""
|
||||
from ormar.queryset.actions import FilterAction, OrderAction, SelectAction
|
||||
from ormar.queryset.clause import and_, or_
|
||||
from ormar.queryset.field_accessor import FieldAccessor
|
||||
from ormar.queryset.filter_query import FilterQuery
|
||||
from ormar.queryset.limit_query import LimitQuery
|
||||
from ormar.queryset.offset_query import OffsetQuery
|
||||
@ -20,4 +21,5 @@ __all__ = [
|
||||
"SelectAction",
|
||||
"and_",
|
||||
"or_",
|
||||
"FieldAccessor",
|
||||
]
|
||||
|
||||
@ -26,6 +26,23 @@ FILTER_OPERATORS = {
|
||||
"lt": "__lt__",
|
||||
"lte": "__le__",
|
||||
}
|
||||
METHODS_TO_OPERATORS = {
|
||||
"__eq__": "exact",
|
||||
"__mod__": "contains",
|
||||
"__gt__": "gt",
|
||||
"__ge__": "gte",
|
||||
"__lt__": "lt",
|
||||
"__le__": "lte",
|
||||
"iexact": "iexact",
|
||||
"contains": "contains",
|
||||
"icontains": "icontains",
|
||||
"startswith": "startswith",
|
||||
"istartswith": "istartswith",
|
||||
"endswith": "endswith",
|
||||
"iendswith": "iendswith",
|
||||
"isnull": "isnull",
|
||||
"in": "in",
|
||||
}
|
||||
ESCAPE_CHARACTERS = ["%", "_"]
|
||||
|
||||
|
||||
|
||||
102
ormar/queryset/field_accessor.py
Normal file
102
ormar/queryset/field_accessor.py
Normal file
@ -0,0 +1,102 @@
|
||||
from typing import Any
|
||||
|
||||
from ormar.queryset.actions import OrderAction
|
||||
from ormar.queryset.actions import FilterAction
|
||||
from ormar.queryset.actions.filter_action import METHODS_TO_OPERATORS
|
||||
|
||||
|
||||
class FieldAccessor:
|
||||
def __init__(
|
||||
self, source_model=None, field=None, model=None, access_chain: str = ""
|
||||
):
|
||||
self._source_model = source_model
|
||||
self._field = field
|
||||
self._model = model
|
||||
self._access_chain = access_chain
|
||||
|
||||
def __getattr__(self, item: str) -> Any:
|
||||
if self._field and item == self._field.name:
|
||||
return self._field
|
||||
|
||||
if item in self._model.Meta.model_fields:
|
||||
field = self._model.Meta.model_fields.get(item)
|
||||
if field.is_relation:
|
||||
return FieldAccessor(
|
||||
source_model=self._source_model,
|
||||
model=field.to,
|
||||
access_chain=self._access_chain + f"__{item}",
|
||||
)
|
||||
else:
|
||||
return FieldAccessor(
|
||||
source_model=self._source_model,
|
||||
field=field,
|
||||
access_chain=self._access_chain + f"__{item}",
|
||||
)
|
||||
return object.__getattribute__(self, item)
|
||||
|
||||
def _check_field(self) -> None:
|
||||
if not self._field:
|
||||
raise AttributeError(
|
||||
"Cannot filter by Model, you need to provide model name"
|
||||
)
|
||||
|
||||
def _select_operator(self, op: str, other: Any) -> FilterAction:
|
||||
self._check_field()
|
||||
return FilterAction(
|
||||
filter_str=self._access_chain + f"__{METHODS_TO_OPERATORS[op]}",
|
||||
value=other,
|
||||
model_cls=self._source_model,
|
||||
)
|
||||
|
||||
def __eq__(self, other: Any) -> FilterAction: # type: ignore
|
||||
return self._select_operator(op="__eq__", other=other)
|
||||
|
||||
def __ge__(self, other: Any) -> FilterAction:
|
||||
return self._select_operator(op="__ge__", other=other)
|
||||
|
||||
def __gt__(self, other: Any) -> FilterAction:
|
||||
return self._select_operator(op="__gt__", other=other)
|
||||
|
||||
def __le__(self, other: Any) -> FilterAction:
|
||||
return self._select_operator(op="__le__", other=other)
|
||||
|
||||
def __lt__(self, other) -> FilterAction:
|
||||
return self._select_operator(op="__lt__", other=other)
|
||||
|
||||
def __mod__(self, other) -> FilterAction:
|
||||
return self._select_operator(op="__mod__", other=other)
|
||||
|
||||
def __contains__(self, item) -> FilterAction:
|
||||
return self._select_operator(op="in", other=item)
|
||||
|
||||
def iexact(self, other) -> FilterAction:
|
||||
return self._select_operator(op="iexact", other=other)
|
||||
|
||||
def contains(self, other) -> FilterAction:
|
||||
return self._select_operator(op="contains", other=other)
|
||||
|
||||
def icontains(self, other) -> FilterAction:
|
||||
return self._select_operator(op="icontains", other=other)
|
||||
|
||||
def startswith(self, other) -> FilterAction:
|
||||
return self._select_operator(op="startswith", other=other)
|
||||
|
||||
def istartswith(self, other) -> FilterAction:
|
||||
return self._select_operator(op="istartswith", other=other)
|
||||
|
||||
def endswith(self, other) -> FilterAction:
|
||||
return self._select_operator(op="endswith", other=other)
|
||||
|
||||
def iendswith(self, other) -> FilterAction:
|
||||
return self._select_operator(op="iendswith", other=other)
|
||||
|
||||
def isnull(self, other) -> FilterAction:
|
||||
return self._select_operator(op="isnull", other=other)
|
||||
|
||||
def asc(self) -> OrderAction:
|
||||
return OrderAction(order_str=self._access_chain, model_cls=self._source_model)
|
||||
|
||||
def desc(self) -> OrderAction:
|
||||
return OrderAction(
|
||||
order_str="-" + self._access_chain, model_cls=self._source_model
|
||||
)
|
||||
Reference in New Issue
Block a user