1from typing import Any, TYPE_CHECKING, Type 2 3from ormar.queryset.actions import OrderAction 4from ormar.queryset.actions.filter_action import METHODS_TO_OPERATORS 5from ormar.queryset.clause import FilterGroup 6 7if TYPE_CHECKING: # pragma: no cover 8 from ormar import BaseField, Model 9 10 11class FieldAccessor: 12 """ 13 Helper to access ormar fields directly from Model class also for nested 14 models attributes. 15 """ 16 17 def __init__( 18 self, 19 source_model: Type["Model"], 20 field: "BaseField" = None, 21 model: Type["Model"] = None, 22 access_chain: str = "", 23 ) -> None: 24 self._source_model = source_model 25 self._field = field 26 self._model = model 27 self._access_chain = access_chain 28 29 def __bool__(self) -> bool: 30 """ 31 Hack to avoid pydantic name check from parent model, returns false 32 33 :return: False 34 :rtype: bool 35 """ 36 return False 37 38 def __getattr__(self, item: str) -> Any: 39 """ 40 Accessor return new accessor for each field and nested models. 41 Thanks to that operator overload is possible to use in filter. 42 43 :param item: attribute name 44 :type item: str 45 :return: FieldAccessor for field or nested model 46 :rtype: ormar.queryset.field_accessor.FieldAccessor 47 """ 48 if self._field and item == self._field.name: 49 return self._field 50 51 if self._model and item in self._model.Meta.model_fields: 52 field = self._model.Meta.model_fields[item] 53 if field.is_relation: 54 return FieldAccessor( 55 source_model=self._source_model, 56 model=field.to, 57 access_chain=self._access_chain + f"__{item}", 58 ) 59 else: 60 return FieldAccessor( 61 source_model=self._source_model, 62 field=field, 63 access_chain=self._access_chain + f"__{item}", 64 ) 65 return object.__getattribute__(self, item) # pragma: no cover 66 67 def _check_field(self) -> None: 68 if not self._field: 69 raise AttributeError( 70 "Cannot filter by Model, you need to provide model name" 71 ) 72 73 def _select_operator(self, op: str, other: Any) -> FilterGroup: 74 self._check_field() 75 filter_kwg = {self._access_chain + f"__{METHODS_TO_OPERATORS[op]}": other} 76 return FilterGroup(**filter_kwg) 77 78 def __eq__(self, other: Any) -> FilterGroup: # type: ignore 79 """ 80 overloaded to work as sql `column = <VALUE>` 81 82 :param other: value to check agains operator 83 :type other: str 84 :return: FilterGroup for operator 85 :rtype: ormar.queryset.clause.FilterGroup 86 """ 87 return self._select_operator(op="__eq__", other=other) 88 89 def __ge__(self, other: Any) -> FilterGroup: 90 """ 91 overloaded to work as sql `column >= <VALUE>` 92 93 :param other: value to check agains operator 94 :type other: str 95 :return: FilterGroup for operator 96 :rtype: ormar.queryset.clause.FilterGroup 97 """ 98 return self._select_operator(op="__ge__", other=other) 99 100 def __gt__(self, other: Any) -> FilterGroup: 101 """ 102 overloaded to work as sql `column > <VALUE>` 103 104 :param other: value to check agains operator 105 :type other: str 106 :return: FilterGroup for operator 107 :rtype: ormar.queryset.clause.FilterGroup 108 """ 109 return self._select_operator(op="__gt__", other=other) 110 111 def __le__(self, other: Any) -> FilterGroup: 112 """ 113 overloaded to work as sql `column <= <VALUE>` 114 115 :param other: value to check agains operator 116 :type other: str 117 :return: FilterGroup for operator 118 :rtype: ormar.queryset.clause.FilterGroup 119 """ 120 return self._select_operator(op="__le__", other=other) 121 122 def __lt__(self, other: Any) -> FilterGroup: 123 """ 124 overloaded to work as sql `column < <VALUE>` 125 126 :param other: value to check agains operator 127 :type other: str 128 :return: FilterGroup for operator 129 :rtype: ormar.queryset.clause.FilterGroup 130 """ 131 return self._select_operator(op="__lt__", other=other) 132 133 def __mod__(self, other: Any) -> FilterGroup: 134 """ 135 overloaded to work as sql `column LIKE '%<VALUE>%'` 136 137 :param other: value to check agains operator 138 :type other: str 139 :return: FilterGroup for operator 140 :rtype: ormar.queryset.clause.FilterGroup 141 """ 142 return self._select_operator(op="__mod__", other=other) 143 144 def __lshift__(self, other: Any) -> FilterGroup: 145 """ 146 overloaded to work as sql `column IN (<VALUE1>, <VALUE2>,...)` 147 148 :param other: value to check agains operator 149 :type other: str 150 :return: FilterGroup for operator 151 :rtype: ormar.queryset.clause.FilterGroup 152 """ 153 return self._select_operator(op="in", other=other) 154 155 def __rshift__(self, other: Any) -> FilterGroup: 156 """ 157 overloaded to work as sql `column IS NULL` 158 159 :param other: value to check agains operator 160 :type other: str 161 :return: FilterGroup for operator 162 :rtype: ormar.queryset.clause.FilterGroup 163 """ 164 return self._select_operator(op="isnull", other=True) 165 166 def in_(self, other: Any) -> FilterGroup: 167 """ 168 works as sql `column IN (<VALUE1>, <VALUE2>,...)` 169 170 :param other: value to check agains operator 171 :type other: str 172 :return: FilterGroup for operator 173 :rtype: ormar.queryset.clause.FilterGroup 174 """ 175 return self._select_operator(op="in", other=other) 176 177 def iexact(self, other: Any) -> FilterGroup: 178 """ 179 works as sql `column = <VALUE>` case-insensitive 180 181 :param other: value to check agains operator 182 :type other: str 183 :return: FilterGroup for operator 184 :rtype: ormar.queryset.clause.FilterGroup 185 """ 186 return self._select_operator(op="iexact", other=other) 187 188 def contains(self, other: Any) -> FilterGroup: 189 """ 190 works as sql `column LIKE '%<VALUE>%'` 191 192 :param other: value to check agains operator 193 :type other: str 194 :return: FilterGroup for operator 195 :rtype: ormar.queryset.clause.FilterGroup 196 """ 197 return self._select_operator(op="contains", other=other) 198 199 def icontains(self, other: Any) -> FilterGroup: 200 """ 201 works as sql `column LIKE '%<VALUE>%'` case-insensitive 202 203 :param other: value to check agains operator 204 :type other: str 205 :return: FilterGroup for operator 206 :rtype: ormar.queryset.clause.FilterGroup 207 """ 208 return self._select_operator(op="icontains", other=other) 209 210 def startswith(self, other: Any) -> FilterGroup: 211 """ 212 works as sql `column LIKE '<VALUE>%'` 213 214 :param other: value to check agains operator 215 :type other: str 216 :return: FilterGroup for operator 217 :rtype: ormar.queryset.clause.FilterGroup 218 """ 219 return self._select_operator(op="startswith", other=other) 220 221 def istartswith(self, other: Any) -> FilterGroup: 222 """ 223 works as sql `column LIKE '%<VALUE>'` case-insensitive 224 225 :param other: value to check agains operator 226 :type other: str 227 :return: FilterGroup for operator 228 :rtype: ormar.queryset.clause.FilterGroup 229 """ 230 return self._select_operator(op="istartswith", other=other) 231 232 def endswith(self, other: Any) -> FilterGroup: 233 """ 234 works as sql `column LIKE '%<VALUE>'` 235 236 :param other: value to check agains operator 237 :type other: str 238 :return: FilterGroup for operator 239 :rtype: ormar.queryset.clause.FilterGroup 240 """ 241 return self._select_operator(op="endswith", other=other) 242 243 def iendswith(self, other: Any) -> FilterGroup: 244 """ 245 works as sql `column LIKE '%<VALUE>'` case-insensitive 246 247 :param other: value to check agains operator 248 :type other: str 249 :return: FilterGroup for operator 250 :rtype: ormar.queryset.clause.FilterGroup 251 """ 252 return self._select_operator(op="iendswith", other=other) 253 254 def isnull(self, other: Any) -> FilterGroup: 255 """ 256 works as sql `column IS NULL` or `IS NOT NULL` 257 258 :param other: value to check agains operator 259 :type other: str 260 :return: FilterGroup for operator 261 :rtype: ormar.queryset.clause.FilterGroup 262 """ 263 return self._select_operator(op="isnull", other=other) 264 265 def asc(self) -> OrderAction: 266 """ 267 works as sql `column asc` 268 269 :return: OrderGroup for operator 270 :rtype: ormar.queryset.actions.OrderGroup 271 """ 272 return OrderAction(order_str=self._access_chain, model_cls=self._source_model) 273 274 def desc(self) -> OrderAction: 275 """ 276 works as sql `column desc` 277 278 :return: OrderGroup for operator 279 :rtype: ormar.queryset.actions.OrderGroup 280 """ 281 return OrderAction( 282 order_str="-" + self._access_chain, model_cls=self._source_model 283 ) 284