From ca8e3315bd0acbf80de06940bc1f27f38ed487b2 Mon Sep 17 00:00:00 2001 From: Jayden Grubb Date: Sat, 17 Jun 2023 21:30:17 +1000 Subject: [PATCH 1/3] server: add implies/suggests named filters Add new named filters: - suggests: find tags that suggest the search criteria - suggested-by: find tags that are suggested by the search criteria - implies: find tags that imply the search criteria - implied-by: find tags that are implied by the search criteria --- .../search/configs/tag_search_config.py | 44 +++++++++++++++++++ server/szurubooru/search/configs/util.py | 35 +++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/server/szurubooru/search/configs/tag_search_config.py b/server/szurubooru/search/configs/tag_search_config.py index 5d4160357..6143be0e3 100644 --- a/server/szurubooru/search/configs/tag_search_config.py +++ b/server/szurubooru/search/configs/tag_search_config.py @@ -96,6 +96,50 @@ def named_filters(self) -> Dict[str, Filter]: ["implication-count"], search_util.create_num_filter(model.Tag.implication_count), ), + ( + ["suggested-by"], + search_util.create_nested_filter( + model.Tag.tag_id, + model.TagSuggestion.child_id, + model.TagSuggestion.parent_id, + model.TagName.tag_id, + model.TagName.name, + search_util.create_str_filter, + ), + ), + ( + ["suggests"], + search_util.create_nested_filter( + model.Tag.tag_id, + model.TagSuggestion.parent_id, + model.TagSuggestion.child_id, + model.TagName.tag_id, + model.TagName.name, + search_util.create_str_filter, + ), + ), + ( + ["implied-by"], + search_util.create_nested_filter( + model.Tag.tag_id, + model.TagImplication.child_id, + model.TagImplication.parent_id, + model.TagName.tag_id, + model.TagName.name, + search_util.create_str_filter, + ), + ), + ( + ["implies"], + search_util.create_nested_filter( + model.Tag.tag_id, + model.TagImplication.parent_id, + model.TagImplication.child_id, + model.TagName.tag_id, + model.TagName.name, + search_util.create_str_filter, + ), + ), ] ) diff --git a/server/szurubooru/search/configs/util.py b/server/szurubooru/search/configs/util.py index 58e6ebe5d..cd4cb438e 100644 --- a/server/szurubooru/search/configs/util.py +++ b/server/szurubooru/search/configs/util.py @@ -226,3 +226,38 @@ def wrapper( return query.filter(expression) return wrapper + + +def create_nested_filter( + left_id_column: SaColumn, + right_id_column: SaColumn, + filter_column: SaColumn, + nested_id_column: SaColumn, + nested_filter_column: SaColumn, + filter_factory: SaColumn, + subquery_decorator: Callable[[SaQuery], None] = None, +) -> Filter: + filter_func = filter_factory(nested_filter_column) + + def wrapper( + query: SaQuery, + criterion: Optional[criteria.BaseCriterion], + negated: bool, + ) -> SaQuery: + assert criterion + nested = db.session.query(nested_id_column.label("foreign_id")) + nested = nested.options(sa.orm.lazyload("*")) + nested = filter_func(nested, criterion, False) + nested = nested.subquery("t") + subquery = db.session.query(right_id_column.label("foreign_id")) + if subquery_decorator: + subquery = subquery_decorator(subquery) + subquery = subquery.options(sa.orm.lazyload("*")) + subquery = subquery.filter(filter_column.in_(nested)) + subquery = subquery.subquery("t") + expression = left_id_column.in_(subquery) + if negated: + expression = ~expression + return query.filter(expression) + + return wrapper From 7b27465937b9634539e21b0ef73eef650b014f75 Mon Sep 17 00:00:00 2001 From: Jayden Grubb Date: Sun, 18 Jun 2023 19:39:36 +1000 Subject: [PATCH 2/3] server/tests: add unit tests for named filters Add unit tests for named filters added in ca8e331 --- .../search/configs/test_tag_search_config.py | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/server/szurubooru/tests/search/configs/test_tag_search_config.py b/server/szurubooru/tests/search/configs/test_tag_search_config.py index 9fe9a80e8..117e96b10 100644 --- a/server/szurubooru/tests/search/configs/test_tag_search_config.py +++ b/server/szurubooru/tests/search/configs/test_tag_search_config.py @@ -370,6 +370,120 @@ def test_filter_by_implication_count( verify_unpaged(input, expected_tag_names) +@pytest.mark.parametrize( + "input,expected_tag_names", + [ + ("suggests:sug1", ["t1", "t3"]), + ("suggests:sug2", ["t1"]), + ("suggests:sug3", ["t2"]), + ("suggests:t1", []), + ("-suggests:sug1", ["sug1", "sug2", "sug3", "t2"]), + ], +) +def test_filter_by_suggests_tags( + verify_unpaged, tag_factory, input, expected_tag_names +): + sug1 = tag_factory(names=["sug1"]) + sug2 = tag_factory(names=["sug2"]) + sug3 = tag_factory(names=["sug3"]) + tag1 = tag_factory(names=["t1"]) + tag2 = tag_factory(names=["t2"]) + tag3 = tag_factory(names=["t3"]) + db.session.add_all([sug1, sug3, tag2, sug2, tag1, tag3]) + tag1.suggestions.append(sug1) + tag1.suggestions.append(sug2) + tag2.suggestions.append(sug3) + tag3.suggestions.append(sug1) + db.session.flush() + verify_unpaged(input, expected_tag_names) + + +@pytest.mark.parametrize( + "input,expected_tag_names", + [ + ("suggested-by:t1", ["sug1", "sug2"]), + ("suggested-by:t2", ["sug3"]), + ("suggested-by:t3", ["sug4", "t2"]), + ("-suggested-by:t3", ["sug1", "sug2", "sug3", "t1", "t3",]), + ], +) +def test_filter_by_suggests_by_tags( + verify_unpaged, tag_factory, input, expected_tag_names +): + sug1 = tag_factory(names=["sug1"]) + sug2 = tag_factory(names=["sug2"]) + sug3 = tag_factory(names=["sug3"]) + sug4 = tag_factory(names=["sug4"]) + tag1 = tag_factory(names=["t1"]) + tag2 = tag_factory(names=["t2"]) + tag3 = tag_factory(names=["t3"]) + db.session.add_all([sug1, sug3, tag2, sug2, tag1, tag3, sug4]) + tag1.suggestions.append(sug1) + tag1.suggestions.append(sug2) + tag2.suggestions.append(sug3) + tag3.suggestions.append(tag2) + tag3.suggestions.append(sug4) + db.session.flush() + verify_unpaged(input, expected_tag_names) + + +@pytest.mark.parametrize( + "input,expected_tag_names", + [ + ("implies:sug1", ["t1", "t3"]), + ("implies:sug2", ["t1"]), + ("implies:sug3", ["t2"]), + ("implies:t1", []), + ("-implies:sug1", ["sug1", "sug2", "sug3", "t2"]), + ], +) +def test_filter_by_implies_tags( + verify_unpaged, tag_factory, input, expected_tag_names +): + sug1 = tag_factory(names=["sug1"]) + sug2 = tag_factory(names=["sug2"]) + sug3 = tag_factory(names=["sug3"]) + tag1 = tag_factory(names=["t1"]) + tag2 = tag_factory(names=["t2"]) + tag3 = tag_factory(names=["t3"]) + db.session.add_all([sug1, sug3, tag2, sug2, tag1, tag3]) + tag1.implications.append(sug1) + tag1.implications.append(sug2) + tag2.implications.append(sug3) + tag3.implications.append(sug1) + db.session.flush() + verify_unpaged(input, expected_tag_names) + + +@pytest.mark.parametrize( + "input,expected_tag_names", + [ + ("implied-by:t1", ["sug1", "sug2"]), + ("implied-by:t2", ["sug3"]), + ("implied-by:t3", ["sug4", "t2",]), + ("-implied-by:t3", ["sug1", "sug2", "sug3", "t1", "t3",]), + ], +) +def test_filter_by_implied_by_tags( + verify_unpaged, tag_factory, input, expected_tag_names +): + sug1 = tag_factory(names=["sug1"]) + sug2 = tag_factory(names=["sug2"]) + sug3 = tag_factory(names=["sug3"]) + sug4 = tag_factory(names=["sug4"]) + tag1 = tag_factory(names=["t1"]) + tag2 = tag_factory(names=["t2"]) + tag3 = tag_factory(names=["t3"]) + db.session.add_all([sug1, sug3, tag2, sug2, tag1, tag3, sug4]) + tag1.implications.append(sug1) + tag1.implications.append(sug2) + tag2.implications.append(sug3) + tag3.implications.append(tag2) + tag3.implications.append(sug4) + db.session.flush() + verify_unpaged(input, expected_tag_names) + + @pytest.mark.parametrize( "input,expected_tag_names", [ From 61b7ed758fe8ec880ec86d20f65dae88dc1b0771 Mon Sep 17 00:00:00 2001 From: Jayden Grubb Date: Sun, 18 Jun 2023 20:28:43 +1000 Subject: [PATCH 3/3] client: add help entries for named filters Add help entries for named filters added in ca8e331 --- client/html/help_search_tags.tpl | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/client/html/help_search_tags.tpl b/client/html/help_search_tags.tpl index b6fbeccd2..f2f5f9d77 100644 --- a/client/html/help_search_tags.tpl +++ b/client/html/help_search_tags.tpl @@ -50,6 +50,22 @@ post-count alias of usages + + suggests + with given suggested tags (accepts wildcards) + + + implies + with given implied tags (accepts wildcards) + + + suggested-by + suggested by given tags (accepts wildcards) + + + implied-by + implied by given tags (accepts wildcards) + suggestion-count with given number of suggestions