server/posts: support aspect-ratio search query
This commit is contained in:
parent
0b21d98c9b
commit
00c3a4320b
6 changed files with 120 additions and 62 deletions
88
API.md
88
API.md
|
@ -696,48 +696,52 @@ data.
|
|||
|
||||
**Named tokens**
|
||||
|
||||
| `<key>` | 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. `<value>` 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. `<value>` can be either `safe`, `sketchy` (or `questionable`) or `unsafe`. |
|
||||
| `rating` | alias of `safety` |
|
||||
| `<key>` | 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. `<value>` 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. `<value>` can be either `safe`, `sketchy` (or `questionable`) or `unsafe`. |
|
||||
| `rating` | alias of `safety` |
|
||||
|
||||
**Sort style tokens**
|
||||
|
||||
|
|
|
@ -90,6 +90,14 @@
|
|||
<td><code>image-area</code></td>
|
||||
<td>having given number of pixels (image width * image height)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>image-aspect-ratio</code></td>
|
||||
<td>having given aspect ratio (image width / image height)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>image-ar</code></td>
|
||||
<td>alias of <code>image-aspect-ratio</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>width</code></td>
|
||||
<td>alias of <code>image-width</code></td>
|
||||
|
@ -102,6 +110,14 @@
|
|||
<td><code>area</code></td>
|
||||
<td>alias of <code>image-area</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>aspect-ratio</code></td>
|
||||
<td>alias of <code>image-aspect-ratio</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>ar</code></td>
|
||||
<td>alias of <code>image-aspect-ratio</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>creation-date</code></td>
|
||||
<td>posted at given date</td>
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]),
|
||||
|
|
Loading…
Reference in a new issue