mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-05-15 22:37:32 -04:00
raise ValueError when querying on private columns
This commit is contained in:
@@ -199,10 +199,18 @@ class QueryFilterBuilder:
|
|||||||
if i == len(group) - 1:
|
if i == len(group) - 1:
|
||||||
return consolidated_group_builder.self_group()
|
return consolidated_group_builder.self_group()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _get_model_attr(cls, model: type[SqlAlchemyBase], attr_name: str) -> InstrumentedAttribute:
|
||||||
|
model_attr: InstrumentedAttribute = getattr(model, attr_name)
|
||||||
|
if getattr(model_attr, "info", {}).get("private"):
|
||||||
|
raise ValueError(f"cannot filter on private field '{model.__name__}.{attr_name}'")
|
||||||
|
|
||||||
|
return model_attr
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_model_and_model_attr_from_attr_string[Model: SqlAlchemyBase](
|
def get_model_and_model_attr_from_attr_string[Model: SqlAlchemyBase](
|
||||||
cls, attr_string: str, model: type[Model], *, query: sa.Select | None = None
|
cls, attr_string: str, model: type[Model], *, query: sa.Select | None = None
|
||||||
) -> tuple[SqlAlchemyBase, InstrumentedAttribute, sa.Select | None]:
|
) -> tuple[type[SqlAlchemyBase], InstrumentedAttribute, sa.Select | None]:
|
||||||
"""
|
"""
|
||||||
Take an attribute string and traverse a database model and its relationships to get the desired
|
Take an attribute string and traverse a database model and its relationships to get the desired
|
||||||
model and model attribute. Optionally provide a query to apply the necessary table joins.
|
model and model attribute. Optionally provide a query to apply the necessary table joins.
|
||||||
@@ -222,17 +230,17 @@ class QueryFilterBuilder:
|
|||||||
if not attribute_chain:
|
if not attribute_chain:
|
||||||
raise ValueError("invalid query string: attribute name cannot be empty")
|
raise ValueError("invalid query string: attribute name cannot be empty")
|
||||||
|
|
||||||
current_model: SqlAlchemyBase = model # type: ignore
|
current_model: type[SqlAlchemyBase] = model
|
||||||
for i, attribute_link in enumerate(attribute_chain):
|
for i, attribute_link in enumerate(attribute_chain):
|
||||||
try:
|
try:
|
||||||
model_attr = getattr(current_model, attribute_link)
|
model_attr = cls._get_model_attr(current_model, attribute_link)
|
||||||
|
|
||||||
# proxied attributes can't be joined to the query directly, so we need to inspect the proxy
|
# proxied attributes can't be joined to the query directly, so we need to inspect the proxy
|
||||||
# and get the actual model and its attribute
|
# and get the actual model and its attribute
|
||||||
if isinstance(model_attr, AssociationProxyInstance):
|
if isinstance(model_attr, AssociationProxyInstance):
|
||||||
proxied_attribute_link = model_attr.target_collection
|
proxied_attribute_link = model_attr.target_collection
|
||||||
next_attribute_link = model_attr.value_attr
|
next_attribute_link = model_attr.value_attr
|
||||||
model_attr = getattr(current_model, proxied_attribute_link)
|
model_attr = cls._get_model_attr(current_model, proxied_attribute_link)
|
||||||
|
|
||||||
if query is not None:
|
if query is not None:
|
||||||
query = query.join(model_attr, isouter=True)
|
query = query.join(model_attr, isouter=True)
|
||||||
@@ -240,7 +248,7 @@ class QueryFilterBuilder:
|
|||||||
mapper = sa.inspect(current_model)
|
mapper = sa.inspect(current_model)
|
||||||
relationship = mapper.relationships[proxied_attribute_link]
|
relationship = mapper.relationships[proxied_attribute_link]
|
||||||
current_model = relationship.mapper.class_
|
current_model = relationship.mapper.class_
|
||||||
model_attr = getattr(current_model, next_attribute_link)
|
model_attr = cls._get_model_attr(current_model, next_attribute_link)
|
||||||
|
|
||||||
# at the end of the chain there are no more relationships to inspect
|
# at the end of the chain there are no more relationships to inspect
|
||||||
if i == len(attribute_chain) - 1:
|
if i == len(attribute_chain) - 1:
|
||||||
@@ -299,7 +307,9 @@ class QueryFilterBuilder:
|
|||||||
if len(value) == 1:
|
if len(value) == 1:
|
||||||
element = model_attr.in_(value)
|
element = model_attr.in_(value)
|
||||||
else:
|
else:
|
||||||
primary_model_attr: InstrumentedAttribute = getattr(model, component.attribute_name.split(".")[0])
|
primary_model_attr: InstrumentedAttribute = cls._get_model_attr(
|
||||||
|
model, component.attribute_name.split(".")[0]
|
||||||
|
)
|
||||||
element = sa.and_(*(primary_model_attr.any(model_attr == v) for v in value))
|
element = sa.and_(*(primary_model_attr.any(model_attr == v) for v in value))
|
||||||
elif component.relationship is RelationalKeyword.LIKE:
|
elif component.relationship is RelationalKeyword.LIKE:
|
||||||
element = model_attr.ilike(value)
|
element = model_attr.ilike(value)
|
||||||
@@ -368,7 +378,7 @@ class QueryFilterBuilder:
|
|||||||
else:
|
else:
|
||||||
component = cast(QueryFilterBuilderComponent, component)
|
component = cast(QueryFilterBuilderComponent, component)
|
||||||
base_attribute_name = component.attribute_name.split(".")[-1]
|
base_attribute_name = component.attribute_name.split(".")[-1]
|
||||||
model_attr = getattr(attr_model_map[i], base_attribute_name)
|
model_attr = self._get_model_attr(attr_model_map[i], base_attribute_name)
|
||||||
|
|
||||||
if (column_alias := column_aliases.get(base_attribute_name)) is not None:
|
if (column_alias := column_aliases.get(base_attribute_name)) is not None:
|
||||||
model_attr = column_alias
|
model_attr = column_alias
|
||||||
|
|||||||
Reference in New Issue
Block a user