summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/szurubooru/search/configs/post_search_config.py26
-rw-r--r--server/szurubooru/tests/search/configs/test_post_search_config.py33
2 files changed, 59 insertions, 0 deletions
diff --git a/server/szurubooru/search/configs/post_search_config.py b/server/szurubooru/search/configs/post_search_config.py
index eda4084..c8eed18 100644
--- a/server/szurubooru/search/configs/post_search_config.py
+++ b/server/szurubooru/search/configs/post_search_config.py
@@ -122,6 +122,31 @@ def _pool_filter(
)(query, criterion, negated)
+# includes the given post itself
+def _similar_filter(
+ query: SaQuery, criterion: Optional[criteria.BaseCriterion], negated: bool
+) -> SaQuery:
+ assert criterion
+ # subquery for tags of the given post (post id in criterion)
+ filter_func_tag = search_util.create_num_filter(model.PostTag.post_id)
+ tag_query = db.session.query(model.PostTag.tag_id)
+ tag_query = filter_func_tag(tag_query, criterion, False)
+ tag_query = tag_query.subquery("source_tags")
+
+ # subquery for posts with matching tags
+ pt_alias = sa.orm.aliased(model.PostTag)
+ subquery = (
+ db.session.query(pt_alias.post_id)
+ .filter(pt_alias.tag_id.in_(tag_query))
+ .group_by(pt_alias.post_id)
+ .subquery("similar_posts")
+ )
+ expr = model.Post.post_id.in_(subquery)
+ if negated:
+ expr = ~expr
+ return query.filter(expr)
+
+
def _create_metric_num_filter(name: str):
def wrapper(
query: SaQuery,
@@ -414,6 +439,7 @@ class PostSearchConfig(BaseSearchConfig):
),
),
(["pool"], _pool_filter),
+ (["similar"], _similar_filter),
]
))
return filters
diff --git a/server/szurubooru/tests/search/configs/test_post_search_config.py b/server/szurubooru/tests/search/configs/test_post_search_config.py
index 84e1601..3a347de 100644
--- a/server/szurubooru/tests/search/configs/test_post_search_config.py
+++ b/server/szurubooru/tests/search/configs/test_post_search_config.py
@@ -965,3 +965,36 @@ def test_around_query(
db.session.add_all([tag_a, tag_b, tag_c, post1, post2, post3])
db.session.flush()
verify_around(input, 2, expected_prev_id, expected_next_id)
+
+
+@pytest.mark.parametrize("input,expected_post_ids", [
+ ("similar:1", [6, 4, 1]),
+ ("similar:2", [6, 5, 4, 2]),
+ ("similar:3", [6, 5, 3]),
+ ("similar:4", [6, 5, 4, 2, 1]),
+ ("similar:5", [6, 5, 4, 3, 2]),
+ ("similar:6", [6, 5, 4, 3, 2, 1]),
+ ("-similar:1", [5, 3, 2]),
+ ("-similar:2", [3, 1]),
+ ("-similar:3", [4, 2, 1]),
+ ("-similar:4", [3]),
+ ("-similar:5", [1]),
+ ("-similar:6", []),
+])
+def test_filter_by_similar(
+ post_factory, tag_factory, verify_unpaged, input, expected_post_ids
+):
+ tagA = tag_factory(names=["a"])
+ tagB = tag_factory(names=["b"])
+ tagC = tag_factory(names=["c"])
+ postA = post_factory(id=1, tags=[tagA])
+ postB = post_factory(id=2, tags=[tagB])
+ postC = post_factory(id=3, tags=[tagC])
+ postAB = post_factory(id=4, tags=[tagA, tagB])
+ postBC = post_factory(id=5, tags=[tagB, tagC])
+ postABC = post_factory(id=6, tags=[tagA, tagB, tagC])
+ db.session.add_all(
+ [tagA, tagB, tagC, postA, postB, postC, postAB, postBC, postABC]
+ )
+ db.session.flush()
+ verify_unpaged(input, expected_post_ids, True)