diff --git a/API.md b/API.md index 08e2c5c..8b25a51 100644 --- a/API.md +++ b/API.md @@ -696,48 +696,52 @@ data. **Named tokens** - | `` | Description | - | ------------------ | ---------------------------------------------------------- | - | `id` | having given post number | - | `tag` | having given tag (accepts wildcards) | - | `score` | having given score | - | `uploader` | uploaded by given user (accepts wildcards) | - | `upload` | alias of upload | - | `submit` | alias of upload | - | `comment` | commented by given user (accepts wildcards) | - | `fav` | favorited by given user (accepts wildcards) | - | `tag-count` | having given number of tags | - | `comment-count` | having given number of comments | - | `fav-count` | favorited by given number of users | - | `note-count` | having given number of annotations | - | `note-text` | having given note text (accepts wildcards) | - | `relation-count` | having given number of relations | - | `feature-count` | having been featured given number of times | - | `type` | given type of posts. `` can be either `image`, `animation` (or `animated` or `anim`), `flash` (or `swf`) or `video` (or `webm`). | - | `content-checksum` | having given SHA1 checksum | - | `file-size` | having given file size (in bytes) | - | `image-width` | having given image width (where applicable) | - | `image-height` | having given image height (where applicable) | - | `image-area` | having given number of pixels (image width * image height) | - | `width` | alias of `image-width` | - | `height` | alias of `image-height` | - | `area` | alias of `image-area` | - | `creation-date` | posted at given date | - | `creation-time` | alias of `creation-date` | - | `date` | alias of `creation-date` | - | `time` | alias of `creation-date` | - | `last-edit-date` | edited at given date | - | `last-edit-time` | alias of `last-edit-date` | - | `edit-date` | alias of `last-edit-date` | - | `edit-time` | alias of `last-edit-date` | - | `comment-date` | commented at given date | - | `comment-time` | alias of `comment-date` | - | `fav-date` | last favorited at given date | - | `fav-time` | alias of `fav-date` | - | `feature-date` | featured at given date | - | `feature-time` | alias of `feature-time` | - | `safety` | having given safety. `` can be either `safe`, `sketchy` (or `questionable`) or `unsafe`. | - | `rating` | alias of `safety` | + | `` | Description | + | -------------------- | ---------------------------------------------------------- | + | `id` | having given post number | + | `tag` | having given tag (accepts wildcards) | + | `score` | having given score | + | `uploader` | uploaded by given user (accepts wildcards) | + | `upload` | alias of upload | + | `submit` | alias of upload | + | `comment` | commented by given user (accepts wildcards) | + | `fav` | favorited by given user (accepts wildcards) | + | `tag-count` | having given number of tags | + | `comment-count` | having given number of comments | + | `fav-count` | favorited by given number of users | + | `note-count` | having given number of annotations | + | `note-text` | having given note text (accepts wildcards) | + | `relation-count` | having given number of relations | + | `feature-count` | having been featured given number of times | + | `type` | given type of posts. `` can be either `image`, `animation` (or `animated` or `anim`), `flash` (or `swf`) or `video` (or `webm`). | + | `content-checksum` | having given SHA1 checksum | + | `file-size` | having given file size (in bytes) | + | `image-width` | having given image width (where applicable) | + | `image-height` | having given image height (where applicable) | + | `image-area` | having given number of pixels (image width * image height) | + | `image-aspect-ratio` | having given aspect ratio (image width / image height) | + | `image-ar` | alias of `image-aspect-ratio` | + | `width` | alias of `image-width` | + | `height` | alias of `image-height` | + | `area` | alias of `image-area` | + | `ar` | alias of `image-aspect-ratio` | + | `aspect-ratio` | alias of `image-aspect-ratio` | + | `creation-date` | posted at given date | + | `creation-time` | alias of `creation-date` | + | `date` | alias of `creation-date` | + | `time` | alias of `creation-date` | + | `last-edit-date` | edited at given date | + | `last-edit-time` | alias of `last-edit-date` | + | `edit-date` | alias of `last-edit-date` | + | `edit-time` | alias of `last-edit-date` | + | `comment-date` | commented at given date | + | `comment-time` | alias of `comment-date` | + | `fav-date` | last favorited at given date | + | `fav-time` | alias of `fav-date` | + | `feature-date` | featured at given date | + | `feature-time` | alias of `feature-time` | + | `safety` | having given safety. `` can be either `safe`, `sketchy` (or `questionable`) or `unsafe`. | + | `rating` | alias of `safety` | **Sort style tokens** diff --git a/client/html/help_search_posts.tpl b/client/html/help_search_posts.tpl index 1c4ea86..a9e1a8e 100644 --- a/client/html/help_search_posts.tpl +++ b/client/html/help_search_posts.tpl @@ -90,6 +90,14 @@ image-area having given number of pixels (image width * image height) + + image-aspect-ratio + having given aspect ratio (image width / image height) + + + image-ar + alias of image-aspect-ratio + width alias of image-width @@ -102,6 +110,14 @@ area alias of image-area + + aspect-ratio + alias of image-aspect-ratio + + + ar + alias of image-aspect-ratio + creation-date posted at given date diff --git a/server/szurubooru/model/post.py b/server/szurubooru/model/post.py index 23f52b5..0aa04e5 100644 --- a/server/szurubooru/model/post.py +++ b/server/szurubooru/model/post.py @@ -194,6 +194,7 @@ class Post(Base): .correlate_except(PostTag)) canvas_area = column_property(canvas_width * canvas_height) + canvas_aspect_ratio = column_property(canvas_width / canvas_height) @property def is_featured(self) -> bool: diff --git a/server/szurubooru/search/configs/post_search_config.py b/server/szurubooru/search/configs/post_search_config.py index 003b4c2..34e8653 100644 --- a/server/szurubooru/search/configs/post_search_config.py +++ b/server/szurubooru/search/configs/post_search_config.py @@ -290,6 +290,13 @@ class PostSearchConfig(BaseSearchConfig): search_util.create_num_filter(model.Post.canvas_area) ), + ( + ['image-aspect-ratio', 'image-ar', 'aspect-ratio', 'ar'], + search_util.create_num_filter( + model.Post.canvas_aspect_ratio, + transformer=search_util.float_transformer) + ), + ( ['creation-date', 'creation-time', 'date', 'time'], search_util.create_date_filter(model.Post.creation_time) diff --git a/server/szurubooru/search/configs/util.py b/server/szurubooru/search/configs/util.py index 086f392..c6b9b78 100644 --- a/server/szurubooru/search/configs/util.py +++ b/server/szurubooru/search/configs/util.py @@ -1,4 +1,4 @@ -from typing import Any, Optional, Callable +from typing import Any, Optional, Union, Callable import sqlalchemy as sa from szurubooru import db, errors from szurubooru.func import util @@ -7,6 +7,9 @@ from szurubooru.search.typing import SaColumn, SaQuery from szurubooru.search.configs.base_search_config import Filter +Number = Union[int, float] + + def wildcard_transformer(value: str) -> str: return ( value @@ -16,22 +19,37 @@ def wildcard_transformer(value: str) -> str: .replace('*', '%')) +def integer_transformer(value: str) -> int: + return int(value) + + +def float_transformer(value: str) -> float: + for sep in list('/:'): + if sep in value: + a, b = value.split(sep, 1) + return float(a) / float(b) + return float(value) + + def apply_num_criterion_to_column( - column: Any, criterion: criteria.BaseCriterion) -> Any: + column: Any, + criterion: criteria.BaseCriterion, + transformer: Callable[[str], Number]=integer_transformer) -> SaQuery: try: if isinstance(criterion, criteria.PlainCriterion): - expr = column == int(criterion.value) + expr = column == transformer(criterion.value) elif isinstance(criterion, criteria.ArrayCriterion): - expr = column.in_(int(value) for value in criterion.values) + expr = column.in_(transformer(value) for value in criterion.values) elif isinstance(criterion, criteria.RangedCriterion): assert criterion.min_value or criterion.max_value if criterion.min_value and criterion.max_value: expr = column.between( - int(criterion.min_value), int(criterion.max_value)) + transformer(criterion.min_value), + transformer(criterion.max_value)) elif criterion.min_value: - expr = column >= int(criterion.min_value) + expr = column >= transformer(criterion.min_value) elif criterion.max_value: - expr = column <= int(criterion.max_value) + expr = column <= transformer(criterion.max_value) else: assert False except ValueError: @@ -40,13 +58,15 @@ def apply_num_criterion_to_column( return expr -def create_num_filter(column: Any) -> Filter: +def create_num_filter( + column: Any, + transformer: Callable[[str], Number]=integer_transformer) -> SaQuery: def wrapper( query: SaQuery, criterion: Optional[criteria.BaseCriterion], negated: bool) -> SaQuery: assert criterion - expr = apply_num_criterion_to_column(column, criterion) + expr = apply_num_criterion_to_column(column, criterion, transformer) if negated: expr = ~expr return query.filter(expr) 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 738ee41..551e09c 100644 --- a/server/szurubooru/tests/search/configs/test_post_search_config.py +++ b/server/szurubooru/tests/search/configs/test_post_search_config.py @@ -422,14 +422,19 @@ def test_filter_by_file_size( @pytest.mark.parametrize('input,expected_post_ids', [ ('image-width:100', [1]), - ('image-width:102', [3]), - ('image-width:100,102', [1, 3]), + ('image-width:200', [2]), + ('image-width:100,300', [1, 3]), ('image-height:200', [1]), - ('image-height:202', [3]), - ('image-height:200,202', [1, 3]), - ('image-area:20000', [1]), - ('image-area:20604', [3]), - ('image-area:20000,20604', [1, 3]), + ('image-height:100', [2]), + ('image-height:200,300', [1, 3]), + ('image-area:20000', [1, 2]), + ('image-area:90000', [3]), + ('image-area:20000,90000', [1, 2, 3]), + ('image-ar:1', [3]), + ('image-ar:..0.9', [1]), + ('image-ar:1.1..', [2]), + ('image-ar:1/1..1/1', [3]), + ('image-ar:1:1..1:1', [3]), ]) def test_filter_by_image_size( verify_unpaged, post_factory, input, expected_post_ids): @@ -437,16 +442,21 @@ def test_filter_by_image_size( post2 = post_factory(id=2) post3 = post_factory(id=3) post1.canvas_width = 100 - post2.canvas_width = 101 - post3.canvas_width = 102 post1.canvas_height = 200 - post2.canvas_height = 201 - post3.canvas_height = 202 + post2.canvas_width = 200 + post2.canvas_height = 100 + post3.canvas_width = 300 + post3.canvas_height = 300 db.session.add_all([post1, post2, post3]) db.session.flush() verify_unpaged(input, expected_post_ids) +def test_filter_by_invalid_aspect_ratio(executor): + with pytest.raises(errors.SearchError): + executor.execute('image-ar:1:1:1', page=1, page_size=100) + + @pytest.mark.parametrize('input,expected_post_ids', [ ('creation-date:2014', [1]), ('creation-date:2016', [3]),