wip adding filtering and order by with field chain access instead of strings

This commit is contained in:
collerek
2021-04-17 16:24:42 +02:00
parent fa792404bf
commit c49d21f605
7 changed files with 305 additions and 2 deletions

View File

@ -75,7 +75,7 @@ class UndefinedType: # pragma no cover
Undefined = UndefinedType()
__version__ = "0.10.3"
__version__ = "0.10.4"
__all__ = [
"Integer",
"BigInteger",

View File

@ -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)

View File

@ -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",
]

View File

@ -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 = ["%", "_"]

View 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
)