mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-12-08 11:25:17 -05:00
Feature: Global Timeline (#2265)
* extended query filter to accept nested tables * decoupled timeline api from recipe slug * modified frontend to use simplified events api * fixed nested loop index ghosting * updated existing tests * gave mypy a snack * added tests for nested queries * fixed "last made" render error * decoupled recipe timeline from dialog * removed unused props * tweaked recipe get_all to accept ids * created group global timeline added new timeline page to sidebar reformatted the recipe timeline added vertical option to recipe card mobile * extracted timeline item into its own component * fixed apploader centering * added paginated scrolling to recipe timeline * added sort direction config fixed infinite scroll on dialog fixed hasMore var not resetting during instantiation * added sort direction to user preferences * updated API docs with new query filter feature * better error tracing * fix for recipe not found response * simplified recipe crud route for slug/id added test for fetching by slug/id * made query filter UUID validation clearer * moved timeline menu option below shopping lists --------- Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
This commit is contained in:
@@ -4,14 +4,18 @@ import datetime
|
||||
import re
|
||||
from enum import Enum
|
||||
from typing import Any, TypeVar, cast
|
||||
from uuid import UUID
|
||||
|
||||
from dateutil import parser as date_parser
|
||||
from dateutil.parser import ParserError
|
||||
from humps import decamelize
|
||||
from sqlalchemy import Select, bindparam, text
|
||||
from sqlalchemy import Select, bindparam, inspect, text
|
||||
from sqlalchemy.orm import Mapper
|
||||
from sqlalchemy.sql import sqltypes
|
||||
from sqlalchemy.sql.expression import BindParameter
|
||||
|
||||
from mealie.db.models._model_utils.guid import GUID
|
||||
|
||||
Model = TypeVar("Model")
|
||||
|
||||
|
||||
@@ -87,14 +91,51 @@ class QueryFilter:
|
||||
# we explicitly mark this as a filter component instead cast doesn't
|
||||
# actually do anything at runtime
|
||||
component = cast(QueryFilterComponent, component)
|
||||
attribute_chain = component.attribute_name.split(".")
|
||||
if not attribute_chain:
|
||||
raise ValueError("invalid query string: attribute name cannot be empty")
|
||||
|
||||
if not hasattr(model, component.attribute_name):
|
||||
raise ValueError(f"invalid query string: '{component.attribute_name}' does not exist on this schema")
|
||||
attr_model: Any = model
|
||||
for j, attribute_link in enumerate(attribute_chain):
|
||||
# last element
|
||||
if j == len(attribute_chain) - 1:
|
||||
if not hasattr(attr_model, attribute_link):
|
||||
raise ValueError(
|
||||
f"invalid query string: '{component.attribute_name}' does not exist on this schema"
|
||||
)
|
||||
|
||||
attr_value = attribute_link
|
||||
if j:
|
||||
# use the nested table name, rather than the dot notation
|
||||
component.attribute_name = f"{attr_model.__table__.name}.{attr_value}"
|
||||
|
||||
continue
|
||||
|
||||
# join on nested model
|
||||
try:
|
||||
query = query.join(getattr(attr_model, attribute_link))
|
||||
|
||||
mapper: Mapper = inspect(attr_model)
|
||||
relationship = mapper.relationships[attribute_link]
|
||||
attr_model = relationship.mapper.class_
|
||||
|
||||
except (AttributeError, KeyError) as e:
|
||||
raise ValueError(
|
||||
f"invalid query string: '{component.attribute_name}' does not exist on this schema"
|
||||
) from e
|
||||
|
||||
# convert values to their proper types
|
||||
attr = getattr(model, component.attribute_name)
|
||||
attr = getattr(attr_model, attr_value)
|
||||
value: Any = component.value
|
||||
|
||||
if isinstance(attr.type, (GUID)):
|
||||
try:
|
||||
# we don't set value since a UUID is functionally identical to a string here
|
||||
UUID(value)
|
||||
|
||||
except ValueError as e:
|
||||
raise ValueError(f"invalid query string: invalid UUID '{component.value}'") from e
|
||||
|
||||
if isinstance(attr.type, (sqltypes.Date, sqltypes.DateTime)):
|
||||
# TODO: add support for IS NULL and IS NOT NULL
|
||||
# in the meantime, this will work for the specific usecase of non-null dates/datetimes
|
||||
|
||||
Reference in New Issue
Block a user