server/tags: add tag listing

This commit is contained in:
rr- 2016-04-16 20:55:15 +02:00
parent c71c082000
commit 61d2fb88ea
10 changed files with 664 additions and 130 deletions

95
API.md
View file

@ -83,7 +83,82 @@ data.
## Listing tags
Not yet implemented.
- **Request**
`GET /tags/?page=<page>&pageSize=<page-size>&query=<query>`
- **Output**
```json5
{
"query": "haruhi",
"tags": [
<tag>,
<tag>,
<tag>,
<tag>,
<tag>
],
"page": 1,
"pageSize": 5,
"total": 7
}
```
...where `<tag>` is a [tag resource](#tag) and `query` contains standard
[search query](#search).
- **Errors**
- privileges are too low
- **Description**
Searches for tags.
**Anonymous tokens**
Same as `name` token.
**Named tokens**
| `<value>` | Description |
| ------------------- | ------------------------------------- |
| `name` | having given name (accepts wildcards) |
| `category` | having given category |
| `creation-date` | created at given date |
| `creation-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` |
| `usages` | used in given number of posts |
| `usage-count` | alias of `usages` |
| `post-count` | alias of `usages` |
| `suggestion-count` | with given number of suggestions |
| `implication-count` | with given number of implications |
**Order tokens**
| `<value>` | Description |
| ------------------- | ---------------------------- |
| `random` | as random as it can get |
| `name` | A to Z |
| `category` | category (A to Z) |
| `creation-date` | recently created first |
| `creation-time` | alias of `creation-date` |
| `last-edit-date` | recently edited first |
| `last-edit-time` | alias of `creation-time` |
| `edit-date` | alias of `creation-time` |
| `edit-time` | alias of `creation-time` |
| `usages` | used in most posts first |
| `usage-count` | alias of `usages` |
| `post-count` | alias of `usages` |
| `suggestion-count` | with most suggestions first |
| `implication-count` | with most implications first |
**Special tokens**
None.
## Creating tag
@ -264,15 +339,15 @@ Not yet implemented.
**Named tokens**
| `<value>` | Description |
| ----------------- | ------------------------------------------------ |
| `name` | having given name (accepts wildcards) |
| `creation-date` | registered at given date |
| `creation-time` | alias of `creation-date` |
| `last-login-date` | whose most recent login date matches given date |
| `last-login-time` | alias of `last-login-date` |
| `login-date` | alias of `last-login-date` |
| `login-time` | alias of `last-login-date` |
| `<value>` | Description |
| ----------------- | ----------------------------------------------- |
| `name` | having given name (accepts wildcards) |
| `creation-date` | registered at given date |
| `creation-time` | alias of `creation-date` |
| `last-login-date` | whose most recent login date matches given date |
| `last-login-time` | alias of `last-login-date` |
| `login-date` | alias of `last-login-date` |
| `login-time` | alias of `last-login-date` |
**Order tokens**

View file

@ -345,3 +345,135 @@ most, uploaded by user Pirate.</p>
<p><strong>Special tokens</strong></p>
<p>None.</p>
<h1 id='tag-search-help'>Tag search tokens</h1>
<p><strong>Anonymous tokens</strong></p>
<p>Same as <code>name</code> token.</p>
<p><strong>Named tokens</strong></p>
<table>
<tbody>
<tr>
<td><code>name</code></td>
<td>having given name (accepts wildcards)</td>
</tr>
<tr>
<td><code>category</code></td>
<td>having given category</td>
</tr>
<tr>
<td><code>creation-date</code></td>
<td>created at given date</td>
</tr>
<tr>
<td><code>creation-time</code></td>
<td>alias of <code>creation-date</code></td>
</tr>
<tr>
<td><code>last-edit-date</code></td>
<td>edited at given date</td>
</tr>
<tr>
<td><code>last-edit-time</code></td>
<td>alias of <code>last-edit-date</code></td>
</tr>
<tr>
<td><code>edit-date</code></td>
<td>alias of <code>last-edit-date</code></td>
</tr>
<tr>
<td><code>edit-time</code></td>
<td>alias of <code>last-edit-date</code></td>
</tr>
<tr>
<td><code>usages</code></td>
<td>used in given number of posts</td>
</tr>
<tr>
<td><code>usage-count</code></td>
<td>alias of <code>usages</code></td>
</tr>
<tr>
<td><code>post-count</code></td>
<td>alias of <code>usages</code></td>
</tr>
<tr>
<td><code>suggestion-count</code></td>
<td>with given number of suggestions</td>
</tr>
<tr>
<td><code>implication-count</code></td>
<td>with given number of implications</td>
</tr>
</tbody>
</table>
<p><strong>Order tokens</strong></p>
<table>
<tbody>
<tr>
<td><code>random</code></td>
<td>as random as it can get</td>
</tr>
<tr>
<td><code>name</code></td>
<td>A to Z</td>
</tr>
<tr>
<td><code>category</code></td>
<td>category (A to Z)</td>
</tr>
<tr>
<td><code>creation-date</code></td>
<td>recently created first</td>
</tr>
<tr>
<td><code>creation-time</code></td>
<td>alias of <code>creation-date</code></td>
</tr>
<tr>
<td><code>last-edit-date</code></td>
<td>recently edited first</td>
</tr>
<tr>
<td><code>last-edit-time</code></td>
<td>alias of <code>creation-time</code></td>
</tr>
<tr>
<td><code>edit-date</code></td>
<td>alias of <code>creation-time</code></td>
</tr>
<tr>
<td><code>edit-time</code></td>
<td>alias of <code>creation-time</code></td>
</tr>
<tr>
<td><code>usages</code></td>
<td>used in most posts first</td>
</tr>
<tr>
<td><code>usage-count</code></td>
<td>alias of <code>usages</code></td>
</tr>
<tr>
<td><code>post-count</code></td>
<td>alias of <code>usages</code></td>
</tr>
<tr>
<td><code>suggestion-count</code></td>
<td>with most suggestions first</td>
</tr>
<tr>
<td><code>implication-count</code></td>
<td>with most implications first</td>
</tr>
</tbody>
</table>
<p><strong>Special tokens</strong></p>
<p>None.</p>

View file

@ -1,4 +1,5 @@
import datetime
from szurubooru import search
from szurubooru.util import auth, tags
from szurubooru.api.base_api import BaseApi
@ -15,8 +16,25 @@ def _serialize_tag(tag):
}
class TagListApi(BaseApi):
def __init__(self):
super().__init__()
self._search_executor = search.SearchExecutor(search.TagSearchConfig())
def get(self, ctx):
raise NotImplementedError()
auth.verify_privilege(ctx.user, 'tags:list')
query = ctx.get_param_as_string('query')
page = ctx.get_param_as_int('page', default=1, min=1)
page_size = ctx.get_param_as_int(
'pageSize', default=100, min=1, max=100)
count, tag_list = self._search_executor.execute(
ctx.session, query, page, page_size)
return {
'query': query,
'page': page,
'pageSize': page_size,
'total': count,
'tags': [_serialize_tag(tag) for tag in tag_list],
}
def post(self, ctx):
auth.verify_privilege(ctx.user, 'tags:create')

View file

@ -2,3 +2,4 @@
from szurubooru.search.search_executor import SearchExecutor
from szurubooru.search.user_search_config import UserSearchConfig
from szurubooru.search.tag_search_config import TagSearchConfig

View file

@ -3,74 +3,9 @@ import szurubooru.errors
from szurubooru.util import misc
from szurubooru.search import criteria
def _apply_num_criterion_to_column(column, query, criterion):
''' Decorate SQLAlchemy filter on given column using supplied criterion. '''
if isinstance(criterion, criteria.StringSearchCriterion):
expr = column == criterion.value
elif isinstance(criterion, criteria.ArraySearchCriterion):
expr = column.in_(criterion.values)
elif isinstance(criterion, criteria.RangedSearchCriterion):
expr = column.between(criterion.min_value, criterion.max_value)
else:
assert False
if criterion.negated:
expr = ~expr
return query.filter(expr)
def _apply_date_criterion_to_column(column, query, criterion):
'''
Decorate SQLAlchemy filter on given column using supplied criterion.
Parse the datetime inside the criterion.
'''
if isinstance(criterion, criteria.StringSearchCriterion):
min_date, max_date = misc.parse_time_range(criterion.value)
expr = column.between(min_date, max_date)
elif isinstance(criterion, criteria.ArraySearchCriterion):
expr = sqlalchemy.sql.false()
for value in criterion.values:
min_date, max_date = misc.parse_time_range(value)
expr = expr | column.between(min_date, max_date)
elif isinstance(criterion, criteria.RangedSearchCriterion):
assert criterion.min_value or criterion.max_value
if criterion.min_value and criterion.max_value:
min_date = misc.parse_time_range(criterion.min_value)[0]
max_date = misc.parse_time_range(criterion.max_value)[1]
expr = column.between(min_date, max_date)
elif criterion.min_value:
min_date = misc.parse_time_range(criterion.min_value)[0]
expr = column >= min_date
elif criterion.max_value:
max_date = misc.parse_time_range(criterion.max_value)[1]
expr = column <= max_date
else:
assert False
if criterion.negated:
expr = ~expr
return query.filter(expr)
def _apply_str_criterion_to_column(column, query, criterion):
'''
Decorate SQLAlchemy filter on given column using supplied criterion.
Parse potential wildcards inside the criterion.
'''
if isinstance(criterion, criteria.StringSearchCriterion):
expr = column.like(criterion.value.replace('*', '%'))
elif isinstance(criterion, criteria.ArraySearchCriterion):
expr = sqlalchemy.sql.false()
for value in criterion.values:
expr = expr | column.like(value.replace('*', '%'))
elif isinstance(criterion, criteria.RangedSearchCriterion):
raise szurubooru.errors.SearchError(
'Composite token %r is invalid in this context.' % (criterion,))
else:
assert False
if criterion.negated:
expr = ~expr
return query.filter(expr)
class BaseSearchConfig(object):
ORDER_DESC = 1
ORDER_ASC = 2
ORDER_DESC = 0
ORDER_ASC = 1
def create_query(self, session):
raise NotImplementedError()
@ -91,14 +26,88 @@ class BaseSearchConfig(object):
def order_columns(self):
raise NotImplementedError()
def _create_num_filter(self, column):
return lambda query, criterion: _apply_num_criterion_to_column(
column, query, criterion)
@staticmethod
def _apply_num_criterion_to_column(column, criterion):
'''
Decorate SQLAlchemy filter on given column using supplied criterion.
'''
if isinstance(criterion, criteria.StringSearchCriterion):
expr = column == int(criterion.value)
elif isinstance(criterion, criteria.ArraySearchCriterion):
expr = column.in_(int(value) for value in criterion.values)
elif isinstance(criterion, criteria.RangedSearchCriterion):
expr = column.between(
int(criterion.min_value), int(criterion.max_value))
else:
assert False
if criterion.negated:
expr = ~expr
return expr
def _create_date_filter(self, column):
return lambda query, criterion: _apply_date_criterion_to_column(
column, query, criterion)
@staticmethod
def _create_num_filter(column):
return lambda query, criterion: query.filter(
BaseSearchConfig._apply_num_criterion_to_column(column, criterion))
def _create_str_filter(self, column):
return lambda query, criterion: _apply_str_criterion_to_column(
column, query, criterion)
@staticmethod
def _apply_str_criterion_to_column(column, criterion):
'''
Decorate SQLAlchemy filter on given column using supplied criterion.
Parse potential wildcards inside the criterion.
'''
if isinstance(criterion, criteria.StringSearchCriterion):
expr = column.like(criterion.value.replace('*', '%'))
elif isinstance(criterion, criteria.ArraySearchCriterion):
expr = sqlalchemy.sql.false()
for value in criterion.values:
expr = expr | column.like(value.replace('*', '%'))
elif isinstance(criterion, criteria.RangedSearchCriterion):
raise szurubooru.errors.SearchError(
'Composite token %r is invalid in this context.' % (criterion,))
else:
assert False
if criterion.negated:
expr = ~expr
return expr
@staticmethod
def _create_str_filter(column):
return lambda query, criterion: query.filter(
BaseSearchConfig._apply_str_criterion_to_column(column, criterion))
@staticmethod
def _apply_date_criterion_to_column(column, criterion):
'''
Decorate SQLAlchemy filter on given column using supplied criterion.
Parse the datetime inside the criterion.
'''
if isinstance(criterion, criteria.StringSearchCriterion):
min_date, max_date = misc.parse_time_range(criterion.value)
expr = column.between(min_date, max_date)
elif isinstance(criterion, criteria.ArraySearchCriterion):
expr = sqlalchemy.sql.false()
for value in criterion.values:
min_date, max_date = misc.parse_time_range(value)
expr = expr | column.between(min_date, max_date)
elif isinstance(criterion, criteria.RangedSearchCriterion):
assert criterion.min_value or criterion.max_value
if criterion.min_value and criterion.max_value:
min_date = misc.parse_time_range(criterion.min_value)[0]
max_date = misc.parse_time_range(criterion.max_value)[1]
expr = column.between(min_date, max_date)
elif criterion.min_value:
min_date = misc.parse_time_range(criterion.min_value)[0]
expr = column >= min_date
elif criterion.max_value:
max_date = misc.parse_time_range(criterion.max_value)[1]
expr = column <= max_date
else:
assert False
if criterion.negated:
expr = ~expr
return expr
@staticmethod
def _create_date_filter(column):
return lambda query, criterion: query.filter(
BaseSearchConfig._apply_date_criterion_to_column(column, criterion))

View file

@ -48,28 +48,7 @@ class SearchExecutor(object):
def _handle_key_value(self, query, key, value, negated):
if key == 'order':
if value.count(',') == 0:
order = None
elif value.count(',') == 1:
value, order_str = value.split(',')
if order_str == 'asc':
order = self._search_config.ORDER_ASC
elif order_str == 'desc':
order = self._search_config.ORDER_DESC
else:
raise errors.SearchError(
'Unknown search direction: %r.' % order_str)
else:
raise errors.SearchError(
'Too many commas in order search token.')
if negated:
if order == self._search_config.ORDER_DESC:
order = self._search_config.ORDER_ASC
elif order == self._search_config.ORDER_ASC:
order = self._search_config.ORDER_DESC
else:
order = -1
return self._handle_order(query, value, order)
return self._handle_order(query, value, negated)
elif key == 'special':
return self._handle_special(query, value, negated)
else:
@ -97,26 +76,40 @@ class SearchExecutor(object):
'Unknown special token: %r. Available special tokens: %r.' % (
value, list(self._search_config.special_filters.keys())))
def _handle_order(self, query, value, order):
if value in self._search_config.order_columns:
column, default_order = self._search_config.order_columns[value]
if order is None:
order = default_order
elif order == -1:
if default_order == self._search_config.ORDER_ASC:
order = self._search_config.ORDER_DESC
elif default_order == self._search_config.ORDER_DESC:
order = self._search_config.ORDER_ASC
else:
order = self._search_config.ORDER_ASC
def _handle_order(self, query, value, negated):
if value.count(',') == 0:
order_str = None
elif value.count(',') == 1:
value, order_str = value.split(',')
else:
raise errors.SearchError(
'Too many commas in order search token.')
if value not in self._search_config.order_columns:
raise errors.SearchError(
'Unknown search order: %r. Available search orders: %r.' % (
value, list(self._search_config.order_columns.keys())))
column, default_order = self._search_config.order_columns[value]
if order_str == 'asc':
order = self._search_config.ORDER_ASC
elif order_str == 'desc':
order = self._search_config.ORDER_DESC
elif order_str is None:
order = default_order
else:
raise errors.SearchError(
'Unknown search direction: %r.' % order_str)
if negated:
if order == self._search_config.ORDER_ASC:
column = column.asc()
else:
column = column.desc()
return query.order_by(column)
raise errors.SearchError(
'Unknown search order: %r. Available search orders: %r.' % (
value, list(self._search_config.order_columns.keys())))
order = self._search_config.ORDER_DESC
elif order == self._search_config.ORDER_DESC:
order = self._search_config.ORDER_ASC
if order == self._search_config.ORDER_ASC:
column = column.asc()
elif order == self._search_config.ORDER_DESC:
column = column.desc()
return query.order_by(column)
def _create_criterion(self, value, negated):
if '..' in value:

View file

@ -0,0 +1,97 @@
import sqlalchemy
from sqlalchemy.sql.expression import func
from szurubooru import db
from szurubooru.search.base_search_config import BaseSearchConfig
class TagSearchConfig(BaseSearchConfig):
def __init__(self):
self._session = None
def create_query(self, session):
self._session = session
return session.query(db.Tag)
def finalize_query(self, query):
return query.order_by(self._first_name_subquery.asc())
@property
def anonymous_filter(self):
return self._name_filter
@property
def special_filters(self):
return {}
@property
def named_filters(self):
return {
'name': self._name_filter,
'category': self._create_str_filter(db.Tag.category),
'creation-date': self._create_date_filter(db.Tag.creation_time),
'creation-time': self._create_date_filter(db.Tag.creation_time),
'last-edit-date': self._create_date_filter(db.Tag.last_edit_time),
'last-edit-time': self._create_date_filter(db.Tag.last_edit_time),
'edit-date': self._create_date_filter(db.Tag.last_edit_time),
'edit-time': self._create_date_filter(db.Tag.last_edit_time),
'usages': self._create_num_filter(db.Tag.post_count),
'usage-count': self._create_num_filter(db.Tag.post_count),
'post-count': self._create_num_filter(db.Tag.post_count),
'suggestion-count': self._suggestion_count_filter,
'implication-count': self._implication_count_filter,
}
@property
def order_columns(self):
return {
'random': (func.random(), None),
'name': (self._first_name_subquery, self.ORDER_ASC),
'category': (db.Tag.category, self.ORDER_ASC),
'creation-date': (db.Tag.creation_time, self.ORDER_DESC),
'creation-time': (db.Tag.creation_time, self.ORDER_DESC),
'last-edit-date': (db.Tag.last_edit_time, self.ORDER_DESC),
'last-edit-time': (db.Tag.last_edit_time, self.ORDER_DESC),
'edit-date': (db.Tag.last_edit_time, self.ORDER_DESC),
'edit-time': (db.Tag.last_edit_time, self.ORDER_DESC),
'usages': (db.Tag.post_count, self.ORDER_DESC),
'usage-count': (db.Tag.post_count, self.ORDER_DESC),
'post-count': (db.Tag.post_count, self.ORDER_DESC),
'suggestion-count':
(self._suggestion_count_subquery, self.ORDER_DESC),
'implication-count':
(self._implication_count_subquery, self.ORDER_DESC),
}
def _name_filter(self, query, criterion):
str_filter = self._create_str_filter(db.TagName.name)
return query.filter(
db.Tag.tag_id.in_(
str_filter(self._session.query(db.TagName.tag_id), criterion)))
def _suggestion_count_filter(self, query, criterion):
return query.filter(
self._apply_num_criterion_to_column(
self._suggestion_count_subquery, criterion))
def _implication_count_filter(self, query, criterion):
return query.filter(
self._apply_num_criterion_to_column(
self._implication_count_subquery, criterion))
@property
def _first_name_subquery(self):
return sqlalchemy.select([db.TagName.name]) \
.limit(1) \
.where(db.TagName.tag_id == db.Tag.tag_id) \
.as_scalar()
@property
def _suggestion_count_subquery(self):
return sqlalchemy.select([func.count(db.TagSuggestion.child_id)]) \
.where(db.TagSuggestion.parent_id == db.Tag.tag_id) \
.as_scalar()
@property
def _implication_count_subquery(self):
return sqlalchemy.select([func.count(1)]) \
.where(db.TagImplication.parent_id == db.Tag.tag_id) \
.as_scalar()

View file

@ -34,7 +34,7 @@ class UserSearchConfig(BaseSearchConfig):
@property
def order_columns(self):
return {
'random': func.random(),
'random': (None, func.random()),
'name': (db.User.name, self.ORDER_ASC),
'creation-date': (db.User.creation_time, self.ORDER_DESC),
'creation-time': (db.User.creation_time, self.ORDER_DESC),

View file

@ -0,0 +1,176 @@
import datetime
import pytest
from szurubooru import db, errors, search
@pytest.fixture
def executor(session):
search_config = search.TagSearchConfig()
return search.SearchExecutor(search_config)
@pytest.fixture
def verify_unpaged(session, executor):
def verify(input, expected_tag_names):
actual_count, actual_tags = executor.execute(
session, input, page=1, page_size=100)
actual_tag_names = [u.names[0].name for u in actual_tags]
assert actual_count == len(expected_tag_names)
assert actual_tag_names == expected_tag_names
return verify
@pytest.mark.parametrize('input,expected_tag_names', [
('creation-time:2014', ['t1', 't2']),
('creation-date:2014', ['t1', 't2']),
('-creation-time:2014', ['t3']),
('-creation-date:2014', ['t3']),
('creation-time:2014..2014-06', ['t1', 't2']),
('creation-time:2014-06..2015-01-01', ['t2', 't3']),
('creation-time:2014-06..', ['t2', 't3']),
('creation-time:..2014-06', ['t1', 't2']),
('-creation-time:2014..2014-06', ['t3']),
('-creation-time:2014-06..2015-01-01', ['t1']),
('creation-date:2014..2014-06', ['t1', 't2']),
('creation-date:2014-06..2015-01-01', ['t2', 't3']),
('creation-date:2014-06..', ['t2', 't3']),
('creation-date:..2014-06', ['t1', 't2']),
('-creation-date:2014..2014-06', ['t3']),
('-creation-date:2014-06..2015-01-01', ['t1']),
('creation-time:2014-01,2015', ['t1', 't3']),
('creation-date:2014-01,2015', ['t1', 't3']),
('-creation-time:2014-01,2015', ['t2']),
('-creation-date:2014-01,2015', ['t2']),
])
def test_filter_by_creation_time(
verify_unpaged, session, tag_factory, input, expected_tag_names):
tag1 = tag_factory(names=['t1'])
tag2 = tag_factory(names=['t2'])
tat3 = tag_factory(names=['t3'])
tag1.creation_time = datetime.datetime(2014, 1, 1)
tag2.creation_time = datetime.datetime(2014, 6, 1)
tat3.creation_time = datetime.datetime(2015, 1, 1)
session.add_all([tag1, tag2, tat3])
verify_unpaged(input, expected_tag_names)
@pytest.mark.parametrize('input,expected_tag_names', [
('name:tag1', ['tag1']),
('name:tag2', ['tag2']),
('name:none', []),
('name:', []),
('name:*1', ['tag1']),
('name:*2', ['tag2']),
('name:*', ['tag1', 'tag2', 'tag3', 'tag4']),
('name:t*', ['tag1', 'tag2', 'tag3', 'tag4']),
('name:*a*', ['tag1', 'tag2', 'tag3', 'tag4']),
('name:*!*', []),
('name:!*', []),
('name:*!', []),
('-name:tag1', ['tag2', 'tag3', 'tag4']),
('-name:tag2', ['tag1', 'tag3', 'tag4']),
('name:tag1,tag2', ['tag1', 'tag2']),
('-name:tag1,tag3', ['tag2', 'tag4']),
('name:tag4', ['tag4']),
('name:tag4,tag5', ['tag4']),
])
def test_filter_by_name(
session, verify_unpaged, tag_factory, input, expected_tag_names):
session.add(tag_factory(names=['tag1']))
session.add(tag_factory(names=['tag2']))
session.add(tag_factory(names=['tag3']))
session.add(tag_factory(names=['tag4', 'tag5', 'tag6']))
verify_unpaged(input, expected_tag_names)
@pytest.mark.parametrize('input,expected_tag_names', [
('', ['t1', 't2']),
('t1', ['t1']),
('t2', ['t2']),
('t1,t2', ['t1', 't2']),
])
def test_anonymous(
session, verify_unpaged, tag_factory, input, expected_tag_names):
session.add(tag_factory(names=['t1']))
session.add(tag_factory(names=['t2']))
verify_unpaged(input, expected_tag_names)
@pytest.mark.parametrize('input,expected_tag_names', [
('', ['t1', 't2']),
('order:name', ['t1', 't2']),
('-order:name', ['t2', 't1']),
('order:name,asc', ['t1', 't2']),
('order:name,desc', ['t2', 't1']),
('-order:name,asc', ['t2', 't1']),
('-order:name,desc', ['t1', 't2']),
])
def test_order_by_name(
session, verify_unpaged, tag_factory, input, expected_tag_names):
session.add(tag_factory(names=['t2']))
session.add(tag_factory(names=['t1']))
verify_unpaged(input, expected_tag_names)
@pytest.mark.parametrize('input,expected_user_names', [
('', ['t1', 't2', 't3']),
('order:creation-date', ['t3', 't2', 't1']),
('order:creation-time', ['t3', 't2', 't1']),
])
def test_order_by_creation_time(
session, verify_unpaged, tag_factory, input, expected_user_names):
tag1 = tag_factory(names=['t1'])
tag2 = tag_factory(names=['t2'])
tag3 = tag_factory(names=['t3'])
tag1.creation_time = datetime.datetime(1991, 1, 1)
tag2.creation_time = datetime.datetime(1991, 1, 2)
tag3.creation_time = datetime.datetime(1991, 1, 3)
session.add_all([tag3, tag1, tag2])
verify_unpaged(input, expected_user_names)
@pytest.mark.parametrize('input,expected_tag_names', [
('order:suggestion-count', ['t1', 't2', 'sug1', 'sug2', 'sug3']),
])
def test_order_by_suggestion_count(
session, 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'])
session.add_all([sug1, sug3, tag2, sug2, tag1])
session.commit()
tag1.suggestions.append(db.TagSuggestion(tag1.tag_id, sug1.tag_id))
tag1.suggestions.append(db.TagSuggestion(tag1.tag_id, sug2.tag_id))
tag2.suggestions.append(db.TagSuggestion(tag2.tag_id, sug3.tag_id))
verify_unpaged(input, expected_tag_names)
@pytest.mark.parametrize('input,expected_tag_names', [
('order:implication-count', ['t1', 't2', 'sug1', 'sug2', 'sug3']),
])
def test_order_by_implication_count(
session, 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'])
session.add_all([sug1, sug3, tag2, sug2, tag1])
session.commit()
tag1.implications.append(db.TagImplication(tag1.tag_id, sug1.tag_id))
tag1.implications.append(db.TagImplication(tag1.tag_id, sug2.tag_id))
tag2.implications.append(db.TagImplication(tag2.tag_id, sug3.tag_id))
verify_unpaged(input, expected_tag_names)
def test_filter_by_relation_count(session, verify_unpaged, tag_factory):
sug1 = tag_factory(names=['sug1'])
sug2 = tag_factory(names=['sug2'])
imp1 = tag_factory(names=['imp1'])
tag1 = tag_factory(names=['t1'])
tag2 = tag_factory(names=['t2'])
session.add_all([sug1, tag1, sug2, imp1, tag2])
session.commit()
session.add_all([
db.TagSuggestion(tag1.tag_id, sug1.tag_id),
db.TagSuggestion(tag1.tag_id, sug2.tag_id),
db.TagImplication(tag2.tag_id, imp1.tag_id)])
session.commit()
verify_unpaged('suggestion-count:0', ['imp1', 'sug1', 'sug2', 't2'])
verify_unpaged('suggestion-count:1', [])
verify_unpaged('suggestion-count:2', ['t1'])
verify_unpaged('implication-count:0', ['imp1', 'sug1', 'sug2', 't1'])
verify_unpaged('implication-count:1', ['t2'])
verify_unpaged('implication-count:2', [])

View file

@ -140,13 +140,14 @@ def test_order_by_name(
@pytest.mark.parametrize('input,expected_user_names', [
('', ['u1', 'u2', 'u3']),
('order:creation-date', ['u3', 'u2', 'u1']),
('order:creation-time', ['u3', 'u2', 'u1']),
('-order:creation-date', ['u1', 'u2', 'u3']),
('order:creation-date,asc', ['u1', 'u2', 'u3']),
('order:creation-date,desc', ['u3', 'u2', 'u1']),
('-order:creation-date,asc', ['u3', 'u2', 'u1']),
('-order:creation-date,desc', ['u1', 'u2', 'u3']),
])
def test_order_by_name(
def test_order_by_creation_time(
session, verify_unpaged, input, expected_user_names, user_factory):
user1 = user_factory(name='u1')
user2 = user_factory(name='u2')
@ -157,6 +158,38 @@ def test_order_by_name(
session.add_all([user3, user1, user2])
verify_unpaged(input, expected_user_names)
@pytest.mark.parametrize('input,expected_user_names', [
('', ['u1', 'u2', 'u3']),
('order:last-login-date', ['u3', 'u2', 'u1']),
('order:last-login-time', ['u3', 'u2', 'u1']),
('order:login-date', ['u3', 'u2', 'u1']),
('order:login-time', ['u3', 'u2', 'u1']),
])
def test_order_by_name(
session, verify_unpaged, input, expected_user_names, user_factory):
user1 = user_factory(name='u1')
user2 = user_factory(name='u2')
user3 = user_factory(name='u3')
user1.last_login_time = datetime.datetime(1991, 1, 1)
user2.last_login_time = datetime.datetime(1991, 1, 2)
user3.last_login_time = datetime.datetime(1991, 1, 3)
session.add_all([user3, user1, user2])
verify_unpaged(input, expected_user_names)
def test_random_order(session, executor, user_factory):
user1 = user_factory(name='u1')
user2 = user_factory(name='u2')
user3 = user_factory(name='u3')
session.add_all([user3, user1, user2])
actual_count, actual_users = executor.execute(
session, 'order:random', page=1, page_size=100)
actual_user_names = [u.name for u in actual_users]
assert actual_count == 3
assert len(actual_user_names) == 3
assert 'u1' in actual_user_names
assert 'u2' in actual_user_names
assert 'u3' in actual_user_names
@pytest.mark.parametrize('input,expected_error', [
('creation-date:..', errors.SearchError),
('creation-date:bad..', errors.ValidationError),