From f3bb6c28a19dae5348653a2f66233b19670ce7d4 Mon Sep 17 00:00:00 2001
From: rr- <rr-@sakuya.pl>
Date: Sun, 5 Jun 2016 10:39:56 +0200
Subject: [PATCH] server/general: cosmetic fixes

---
 server/szurubooru/api/context.py              |   8 +-
 .../search/configs/base_search_config.py      | 132 +-----------------
 .../search/configs/comment_search_config.py   |  25 ++--
 .../search/configs/post_search_config.py      |  59 ++++----
 .../search/configs/snapshot_search_config.py  |  13 +-
 .../search/configs/tag_search_config.py       |  25 ++--
 .../search/configs/user_search_config.py      |  17 +--
 server/szurubooru/search/configs/util.py      | 124 ++++++++++++++++
 .../test_comment_search_config.py             |   0
 .../{ => configs}/test_post_search_config.py  |   0
 .../{ => configs}/test_tag_search_config.py   |   0
 .../{ => configs}/test_user_search_config.py  |   0
 12 files changed, 202 insertions(+), 201 deletions(-)
 create mode 100644 server/szurubooru/search/configs/util.py
 rename server/szurubooru/tests/search/{ => configs}/test_comment_search_config.py (100%)
 rename server/szurubooru/tests/search/{ => configs}/test_post_search_config.py (100%)
 rename server/szurubooru/tests/search/{ => configs}/test_tag_search_config.py (100%)
 rename server/szurubooru/tests/search/{ => configs}/test_user_search_config.py (100%)

diff --git a/server/szurubooru/api/context.py b/server/szurubooru/api/context.py
index e66873c7..a21cce02 100644
--- a/server/szurubooru/api/context.py
+++ b/server/szurubooru/api/context.py
@@ -2,8 +2,8 @@ import falcon
 from szurubooru import errors
 from szurubooru.func import net
 
-def _lower_first(input):
-    return input[0].lower() + input[1:]
+def _lower_first(source):
+    return source[0].lower() + source[1:]
 
 def _param_wrapper(func):
     def wrapper(self, name, required=False, default=None, **kwargs):
@@ -11,10 +11,10 @@ def _param_wrapper(func):
             value = self.input[name]
             try:
                 value = func(self, value, **kwargs)
-            except errors.InvalidParameterError as e:
+            except errors.InvalidParameterError as ex:
                 raise errors.InvalidParameterError(
                     'Parameter %r is invalid: %s' % (
-                        name, _lower_first(str(e))))
+                        name, _lower_first(str(ex))))
             return value
         if not required:
             return default
diff --git a/server/szurubooru/search/configs/base_search_config.py b/server/szurubooru/search/configs/base_search_config.py
index dfbe4b0f..1dc9d4bd 100644
--- a/server/szurubooru/search/configs/base_search_config.py
+++ b/server/szurubooru/search/configs/base_search_config.py
@@ -1,10 +1,4 @@
-import sqlalchemy
-from szurubooru import db, errors
-from szurubooru.func import util
-from szurubooru.search import criteria, tokens
-
-def wildcard_transformer(value):
-    return value.replace('*', '%')
+from szurubooru.search import tokens
 
 class BaseSearchConfig(object):
     SORT_ASC = tokens.SortToken.SORT_ASC
@@ -34,127 +28,3 @@ class BaseSearchConfig(object):
     @property
     def sort_columns(self):
         return {}
-
-    @staticmethod
-    def _apply_num_criterion_to_column(column, criterion):
-        '''
-        Decorate SQLAlchemy filter on given column using supplied criterion.
-        '''
-        try:
-            if isinstance(criterion, criteria.PlainCriterion):
-                expr = column == int(criterion.value)
-            elif isinstance(criterion, criteria.ArrayCriterion):
-                expr = column.in_(int(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))
-                elif criterion.min_value != '':
-                    expr = column >= int(criterion.min_value)
-                elif criterion.max_value != '':
-                    expr = column <= int(criterion.max_value)
-            else:
-                assert False
-        except ValueError:
-            raise errors.SearchError(
-                'Criterion value %r must be a number.' % (criterion,))
-        return expr
-
-    @staticmethod
-    def _create_num_filter(column):
-        def wrapper(query, criterion, negated):
-            expr = BaseSearchConfig._apply_num_criterion_to_column(
-                column, criterion)
-            if negated:
-                expr = ~expr
-            return query.filter(expr)
-        return wrapper
-
-    @staticmethod
-    def _apply_str_criterion_to_column(
-            column, criterion, transformer=wildcard_transformer):
-        '''
-        Decorate SQLAlchemy filter on given column using supplied criterion.
-        '''
-        if isinstance(criterion, criteria.PlainCriterion):
-            expr = column.ilike(transformer(criterion.value))
-        elif isinstance(criterion, criteria.ArrayCriterion):
-            expr = sqlalchemy.sql.false()
-            for value in criterion.values:
-                expr = expr | column.ilike(transformer(value))
-        elif isinstance(criterion, criteria.RangedCriterion):
-            raise errors.SearchError(
-                'Composite token %r is invalid in this context.' % (criterion,))
-        else:
-            assert False
-        return expr
-
-    @staticmethod
-    def _create_str_filter(column, transformer=wildcard_transformer):
-        def wrapper(query, criterion, negated):
-            expr = BaseSearchConfig._apply_str_criterion_to_column(
-                column, criterion, transformer)
-            if negated:
-                expr = ~expr
-            return query.filter(expr)
-        return wrapper
-
-    @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.PlainCriterion):
-            min_date, max_date = util.parse_time_range(criterion.value)
-            expr = column.between(min_date, max_date)
-        elif isinstance(criterion, criteria.ArrayCriterion):
-            expr = sqlalchemy.sql.false()
-            for value in criterion.values:
-                min_date, max_date = util.parse_time_range(value)
-                expr = expr | column.between(min_date, max_date)
-        elif isinstance(criterion, criteria.RangedCriterion):
-            assert criterion.min_value or criterion.max_value
-            if criterion.min_value and criterion.max_value:
-                min_date = util.parse_time_range(criterion.min_value)[0]
-                max_date = util.parse_time_range(criterion.max_value)[1]
-                expr = column.between(min_date, max_date)
-            elif criterion.min_value:
-                min_date = util.parse_time_range(criterion.min_value)[0]
-                expr = column >= min_date
-            elif criterion.max_value:
-                max_date = util.parse_time_range(criterion.max_value)[1]
-                expr = column <= max_date
-        else:
-            assert False
-        return expr
-
-    @staticmethod
-    def _create_date_filter(column):
-        def wrapper(query, criterion, negated):
-            expr = BaseSearchConfig._apply_date_criterion_to_column(
-                column, criterion)
-            if negated:
-                expr = ~expr
-            return query.filter(expr)
-        return wrapper
-
-    @staticmethod
-    def _create_subquery_filter(
-            left_id_column,
-            right_id_column,
-            filter_column,
-            filter_factory,
-            subquery_decorator=None):
-        filter_func = filter_factory(filter_column)
-        def wrapper(query, criterion, negated):
-            subquery = db.session.query(right_id_column.label('foreign_id'))
-            if subquery_decorator:
-                subquery = subquery_decorator(subquery)
-            subquery = subquery.options(sqlalchemy.orm.lazyload('*'))
-            subquery = filter_func(subquery, criterion, negated)
-            subquery = subquery.subquery('t')
-            return query.filter(left_id_column.in_(subquery))
-        return wrapper
diff --git a/server/szurubooru/search/configs/comment_search_config.py b/server/szurubooru/search/configs/comment_search_config.py
index bc6eea70..b921492f 100644
--- a/server/szurubooru/search/configs/comment_search_config.py
+++ b/server/szurubooru/search/configs/comment_search_config.py
@@ -1,5 +1,6 @@
 from sqlalchemy.sql.expression import func
 from szurubooru import db
+from szurubooru.search.configs import util as search_util
 from szurubooru.search.configs.base_search_config import BaseSearchConfig
 
 class CommentSearchConfig(BaseSearchConfig):
@@ -11,22 +12,22 @@ class CommentSearchConfig(BaseSearchConfig):
 
     @property
     def anonymous_filter(self):
-        return self._create_str_filter(db.Comment.text)
+        return search_util.create_str_filter(db.Comment.text)
 
     @property
     def named_filters(self):
         return {
-            'id': self._create_num_filter(db.Comment.comment_id),
-            'post': self._create_num_filter(db.Comment.post_id),
-            'user': self._create_str_filter(db.User.name),
-            'author': self._create_str_filter(db.User.name),
-            'text': self._create_str_filter(db.Comment.text),
-            'creation-date': self._create_date_filter(db.Comment.creation_time),
-            'creation-time': self._create_date_filter(db.Comment.creation_time),
-            'last-edit-date': self._create_date_filter(db.Comment.last_edit_time),
-            'last-edit-time': self._create_date_filter(db.Comment.last_edit_time),
-            'edit-date': self._create_date_filter(db.Comment.last_edit_time),
-            'edit-time': self._create_date_filter(db.Comment.last_edit_time),
+            'id': search_util.create_num_filter(db.Comment.comment_id),
+            'post': search_util.create_num_filter(db.Comment.post_id),
+            'user': search_util.create_str_filter(db.User.name),
+            'author': search_util.create_str_filter(db.User.name),
+            'text': search_util.create_str_filter(db.Comment.text),
+            'creation-date': search_util.create_date_filter(db.Comment.creation_time),
+            'creation-time': search_util.create_date_filter(db.Comment.creation_time),
+            'last-edit-date': search_util.create_date_filter(db.Comment.last_edit_time),
+            'last-edit-time': search_util.create_date_filter(db.Comment.last_edit_time),
+            'edit-date': search_util.create_date_filter(db.Comment.last_edit_time),
+            'edit-time': search_util.create_date_filter(db.Comment.last_edit_time),
         }
 
     @property
diff --git a/server/szurubooru/search/configs/post_search_config.py b/server/szurubooru/search/configs/post_search_config.py
index 85c25e38..0f4adafd 100644
--- a/server/szurubooru/search/configs/post_search_config.py
+++ b/server/szurubooru/search/configs/post_search_config.py
@@ -3,6 +3,7 @@ from sqlalchemy.sql.expression import func
 from szurubooru import db, errors
 from szurubooru.func import util
 from szurubooru.search import criteria, tokens
+from szurubooru.search.configs import util as search_util
 from szurubooru.search.configs.base_search_config import BaseSearchConfig
 
 def _enum_transformer(available_values, value):
@@ -45,7 +46,7 @@ def _create_score_filter(score):
         user_alias = aliased(db.User)
         score_alias = aliased(db.PostScore)
         expr = score_alias.score == score
-        expr = expr & BaseSearchConfig._apply_str_criterion_to_column(
+        expr = expr & search_util.apply_str_criterion_to_column(
             user_alias.name, criterion)
         if negated:
             expr = ~expr
@@ -106,69 +107,69 @@ class PostSearchConfig(BaseSearchConfig):
 
     @property
     def anonymous_filter(self):
-        return self._create_subquery_filter(
+        return search_util.create_subquery_filter(
             db.Post.post_id,
             db.PostTag.post_id,
             db.TagName.name,
-            self._create_str_filter,
+            search_util.create_str_filter,
             lambda subquery: subquery.join(db.Tag).join(db.TagName))
 
     @property
     def named_filters(self):
         return util.unalias_dict({
-            'id': self._create_num_filter(db.Post.post_id),
-            'tag': self._create_subquery_filter(
+            'id': search_util.create_num_filter(db.Post.post_id),
+            'tag': search_util.create_subquery_filter(
                 db.Post.post_id,
                 db.PostTag.post_id,
                 db.TagName.name,
-                self._create_str_filter,
+                search_util.create_str_filter,
                 lambda subquery: subquery.join(db.Tag).join(db.TagName)),
-            'score': self._create_num_filter(db.Post.score),
+            'score': search_util.create_num_filter(db.Post.score),
             ('uploader', 'upload', 'submit'):
-                self._create_subquery_filter(
+                search_util.create_subquery_filter(
                     db.Post.user_id,
                     db.User.user_id,
                     db.User.name,
-                    self._create_str_filter),
-            'comment': self._create_subquery_filter(
+                    search_util.create_str_filter),
+            'comment': search_util.create_subquery_filter(
                 db.Post.post_id,
                 db.Comment.post_id,
                 db.User.name,
-                self._create_str_filter,
+                search_util.create_str_filter,
                 lambda subquery: subquery.join(db.User)),
-            'fav': self._create_subquery_filter(
+            'fav': search_util.create_subquery_filter(
                 db.Post.post_id,
                 db.PostFavorite.post_id,
                 db.User.name,
-                self._create_str_filter,
+                search_util.create_str_filter,
                 lambda subquery: subquery.join(db.User)),
             'liked': _create_score_filter(1),
             'disliked': _create_score_filter(-1),
-            'tag-count': self._create_num_filter(db.Post.tag_count),
-            'comment-count': self._create_num_filter(db.Post.comment_count),
-            'fav-count': self._create_num_filter(db.Post.favorite_count),
-            'note-count': self._create_num_filter(db.Post.note_count),
-            'feature-count': self._create_num_filter(db.Post.feature_count),
-            'type': self._create_str_filter(db.Post.type, _type_transformer),
-            'file-size': self._create_num_filter(db.Post.file_size),
+            'tag-count': search_util.create_num_filter(db.Post.tag_count),
+            'comment-count': search_util.create_num_filter(db.Post.comment_count),
+            'fav-count': search_util.create_num_filter(db.Post.favorite_count),
+            'note-count': search_util.create_num_filter(db.Post.note_count),
+            'feature-count': search_util.create_num_filter(db.Post.feature_count),
+            'type': search_util.create_str_filter(db.Post.type, _type_transformer),
+            'file-size': search_util.create_num_filter(db.Post.file_size),
             ('image-width', 'width'):
-                self._create_num_filter(db.Post.canvas_width),
+                search_util.create_num_filter(db.Post.canvas_width),
             ('image-height', 'height'):
-                self._create_num_filter(db.Post.canvas_height),
+                search_util.create_num_filter(db.Post.canvas_height),
             ('image-area', 'area'):
-                self._create_num_filter(db.Post.canvas_area),
+                search_util.create_num_filter(db.Post.canvas_area),
             ('creation-date', 'creation-time', 'date', 'time'):
-                self._create_date_filter(db.Post.creation_time),
+                search_util.create_date_filter(db.Post.creation_time),
             ('last-edit-date', 'last-edit-time', 'edit-date', 'edit-time'):
-                self._create_date_filter(db.Post.last_edit_time),
+                search_util.create_date_filter(db.Post.last_edit_time),
             ('comment-date', 'comment-time'):
-                self._create_date_filter(db.Post.last_comment_edit_time),
+                search_util.create_date_filter(db.Post.last_comment_edit_time),
             ('fav-date', 'fav-time'):
-                self._create_date_filter(db.Post.last_favorite_time),
+                search_util.create_date_filter(db.Post.last_favorite_time),
             ('feature-date', 'feature-time'):
-                self._create_date_filter(db.Post.last_feature_time),
+                search_util.create_date_filter(db.Post.last_feature_time),
             ('safety', 'rating'):
-                self._create_str_filter(db.Post.safety, _safety_transformer),
+                search_util.create_str_filter(db.Post.safety, _safety_transformer),
         })
 
     @property
diff --git a/server/szurubooru/search/configs/snapshot_search_config.py b/server/szurubooru/search/configs/snapshot_search_config.py
index a99aee73..a5e7f9de 100644
--- a/server/szurubooru/search/configs/snapshot_search_config.py
+++ b/server/szurubooru/search/configs/snapshot_search_config.py
@@ -1,4 +1,5 @@
 from szurubooru import db
+from szurubooru.search.configs import util as search_util
 from szurubooru.search.configs.base_search_config import BaseSearchConfig
 
 class SnapshotSearchConfig(BaseSearchConfig):
@@ -11,10 +12,10 @@ class SnapshotSearchConfig(BaseSearchConfig):
     @property
     def named_filters(self):
         return {
-            'type': self._create_str_filter(db.Snapshot.resource_type),
-            'id': self._create_str_filter(db.Snapshot.resource_repr),
-            'date': self._create_date_filter(db.Snapshot.creation_time),
-            'time': self._create_date_filter(db.Snapshot.creation_time),
-            'operation': self._create_str_filter(db.Snapshot.operation),
-            'user': self._create_str_filter(db.User.name),
+            'type': search_util.create_str_filter(db.Snapshot.resource_type),
+            'id': search_util.create_str_filter(db.Snapshot.resource_repr),
+            'date': search_util.create_date_filter(db.Snapshot.creation_time),
+            'time': search_util.create_date_filter(db.Snapshot.creation_time),
+            'operation': search_util.create_str_filter(db.Snapshot.operation),
+            'user': search_util.create_str_filter(db.User.name),
         }
diff --git a/server/szurubooru/search/configs/tag_search_config.py b/server/szurubooru/search/configs/tag_search_config.py
index 4422d875..05bbbcd4 100644
--- a/server/szurubooru/search/configs/tag_search_config.py
+++ b/server/szurubooru/search/configs/tag_search_config.py
@@ -2,6 +2,7 @@ from sqlalchemy.orm import subqueryload
 from sqlalchemy.sql.expression import func
 from szurubooru import db
 from szurubooru.func import util
+from szurubooru.search.configs import util as search_util
 from szurubooru.search.configs.base_search_config import BaseSearchConfig
 
 class TagSearchConfig(BaseSearchConfig):
@@ -23,33 +24,35 @@ class TagSearchConfig(BaseSearchConfig):
 
     @property
     def anonymous_filter(self):
-        return self._create_subquery_filter(
+        return search_util.create_subquery_filter(
             db.Tag.tag_id,
             db.TagName.tag_id,
             db.TagName.name,
-            self._create_str_filter)
+            search_util.create_str_filter)
 
     @property
     def named_filters(self):
         return util.unalias_dict({
-            'name': self._create_subquery_filter(
+            'name': search_util.create_subquery_filter(
                 db.Tag.tag_id,
                 db.TagName.tag_id,
                 db.TagName.name,
-                self._create_str_filter),
-            'category': self._create_subquery_filter(
+                search_util.create_str_filter),
+            'category': search_util.create_subquery_filter(
                 db.Tag.category_id,
                 db.TagCategory.tag_category_id,
                 db.TagCategory.name,
-                self._create_str_filter),
+                search_util.create_str_filter),
             ('creation-date', 'creation-time'):
-                self._create_date_filter(db.Tag.creation_time),
+                search_util.create_date_filter(db.Tag.creation_time),
             ('last-edit-date', 'last-edit-time', 'edit-date', 'edit-time'):
-                self._create_date_filter(db.Tag.last_edit_time),
+                search_util.create_date_filter(db.Tag.last_edit_time),
             ('usage-count', 'post-count', 'usages'):
-                self._create_num_filter(db.Tag.post_count),
-            'suggestion-count': self._create_num_filter(db.Tag.suggestion_count),
-            'implication-count': self._create_num_filter(db.Tag.implication_count),
+                search_util.create_num_filter(db.Tag.post_count),
+            'suggestion-count':
+                search_util.create_num_filter(db.Tag.suggestion_count),
+            'implication-count':
+                search_util.create_num_filter(db.Tag.implication_count),
         })
 
     @property
diff --git a/server/szurubooru/search/configs/user_search_config.py b/server/szurubooru/search/configs/user_search_config.py
index 5d50be10..36468919 100644
--- a/server/szurubooru/search/configs/user_search_config.py
+++ b/server/szurubooru/search/configs/user_search_config.py
@@ -1,5 +1,6 @@
 from sqlalchemy.sql.expression import func
 from szurubooru import db
+from szurubooru.search.configs import util as search_util
 from szurubooru.search.configs.base_search_config import BaseSearchConfig
 
 class UserSearchConfig(BaseSearchConfig):
@@ -13,18 +14,18 @@ class UserSearchConfig(BaseSearchConfig):
 
     @property
     def anonymous_filter(self):
-        return self._create_str_filter(db.User.name)
+        return search_util.create_str_filter(db.User.name)
 
     @property
     def named_filters(self):
         return {
-            'name': self._create_str_filter(db.User.name),
-            'creation-date': self._create_date_filter(db.User.creation_time),
-            'creation-time': self._create_date_filter(db.User.creation_time),
-            'last-login-date': self._create_date_filter(db.User.last_login_time),
-            'last-login-time': self._create_date_filter(db.User.last_login_time),
-            'login-date': self._create_date_filter(db.User.last_login_time),
-            'login-time': self._create_date_filter(db.User.last_login_time),
+            'name': search_util.create_str_filter(db.User.name),
+            'creation-date': search_util.create_date_filter(db.User.creation_time),
+            'creation-time': search_util.create_date_filter(db.User.creation_time),
+            'last-login-date': search_util.create_date_filter(db.User.last_login_time),
+            'last-login-time': search_util.create_date_filter(db.User.last_login_time),
+            'login-date': search_util.create_date_filter(db.User.last_login_time),
+            'login-time': search_util.create_date_filter(db.User.last_login_time),
         }
 
     @property
diff --git a/server/szurubooru/search/configs/util.py b/server/szurubooru/search/configs/util.py
new file mode 100644
index 00000000..5e770ae3
--- /dev/null
+++ b/server/szurubooru/search/configs/util.py
@@ -0,0 +1,124 @@
+import sqlalchemy
+from szurubooru import db, errors
+from szurubooru.func import util
+from szurubooru.search import criteria
+
+def wildcard_transformer(value):
+    return value.replace('*', '%')
+
+def apply_num_criterion_to_column(column, criterion):
+    '''
+    Decorate SQLAlchemy filter on given column using supplied criterion.
+    '''
+    try:
+        if isinstance(criterion, criteria.PlainCriterion):
+            expr = column == int(criterion.value)
+        elif isinstance(criterion, criteria.ArrayCriterion):
+            expr = column.in_(int(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))
+            elif criterion.min_value != '':
+                expr = column >= int(criterion.min_value)
+            elif criterion.max_value != '':
+                expr = column <= int(criterion.max_value)
+        else:
+            assert False
+    except ValueError:
+        raise errors.SearchError(
+            'Criterion value %r must be a number.' % (criterion,))
+    return expr
+
+def create_num_filter(column):
+    def wrapper(query, criterion, negated):
+        expr = apply_num_criterion_to_column(
+            column, criterion)
+        if negated:
+            expr = ~expr
+        return query.filter(expr)
+    return wrapper
+
+def apply_str_criterion_to_column(
+        column, criterion, transformer=wildcard_transformer):
+    '''
+    Decorate SQLAlchemy filter on given column using supplied criterion.
+    '''
+    if isinstance(criterion, criteria.PlainCriterion):
+        expr = column.ilike(transformer(criterion.value))
+    elif isinstance(criterion, criteria.ArrayCriterion):
+        expr = sqlalchemy.sql.false()
+        for value in criterion.values:
+            expr = expr | column.ilike(transformer(value))
+    elif isinstance(criterion, criteria.RangedCriterion):
+        raise errors.SearchError(
+            'Composite token %r is invalid in this context.' % (criterion,))
+    else:
+        assert False
+    return expr
+
+def create_str_filter(column, transformer=wildcard_transformer):
+    def wrapper(query, criterion, negated):
+        expr = apply_str_criterion_to_column(
+            column, criterion, transformer)
+        if negated:
+            expr = ~expr
+        return query.filter(expr)
+    return wrapper
+
+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.PlainCriterion):
+        min_date, max_date = util.parse_time_range(criterion.value)
+        expr = column.between(min_date, max_date)
+    elif isinstance(criterion, criteria.ArrayCriterion):
+        expr = sqlalchemy.sql.false()
+        for value in criterion.values:
+            min_date, max_date = util.parse_time_range(value)
+            expr = expr | column.between(min_date, max_date)
+    elif isinstance(criterion, criteria.RangedCriterion):
+        assert criterion.min_value or criterion.max_value
+        if criterion.min_value and criterion.max_value:
+            min_date = util.parse_time_range(criterion.min_value)[0]
+            max_date = util.parse_time_range(criterion.max_value)[1]
+            expr = column.between(min_date, max_date)
+        elif criterion.min_value:
+            min_date = util.parse_time_range(criterion.min_value)[0]
+            expr = column >= min_date
+        elif criterion.max_value:
+            max_date = util.parse_time_range(criterion.max_value)[1]
+            expr = column <= max_date
+    else:
+        assert False
+    return expr
+
+def create_date_filter(column):
+    def wrapper(query, criterion, negated):
+        expr = apply_date_criterion_to_column(
+            column, criterion)
+        if negated:
+            expr = ~expr
+        return query.filter(expr)
+    return wrapper
+
+def create_subquery_filter(
+        left_id_column,
+        right_id_column,
+        filter_column,
+        filter_factory,
+        subquery_decorator=None):
+    filter_func = filter_factory(filter_column)
+    def wrapper(query, criterion, negated):
+        subquery = db.session.query(right_id_column.label('foreign_id'))
+        if subquery_decorator:
+            subquery = subquery_decorator(subquery)
+        subquery = subquery.options(sqlalchemy.orm.lazyload('*'))
+        subquery = filter_func(subquery, criterion, negated)
+        subquery = subquery.subquery('t')
+        return query.filter(left_id_column.in_(subquery))
+    return wrapper
diff --git a/server/szurubooru/tests/search/test_comment_search_config.py b/server/szurubooru/tests/search/configs/test_comment_search_config.py
similarity index 100%
rename from server/szurubooru/tests/search/test_comment_search_config.py
rename to server/szurubooru/tests/search/configs/test_comment_search_config.py
diff --git a/server/szurubooru/tests/search/test_post_search_config.py b/server/szurubooru/tests/search/configs/test_post_search_config.py
similarity index 100%
rename from server/szurubooru/tests/search/test_post_search_config.py
rename to server/szurubooru/tests/search/configs/test_post_search_config.py
diff --git a/server/szurubooru/tests/search/test_tag_search_config.py b/server/szurubooru/tests/search/configs/test_tag_search_config.py
similarity index 100%
rename from server/szurubooru/tests/search/test_tag_search_config.py
rename to server/szurubooru/tests/search/configs/test_tag_search_config.py
diff --git a/server/szurubooru/tests/search/test_user_search_config.py b/server/szurubooru/tests/search/configs/test_user_search_config.py
similarity index 100%
rename from server/szurubooru/tests/search/test_user_search_config.py
rename to server/szurubooru/tests/search/configs/test_user_search_config.py