Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add per column regex filtering #108

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions datatables/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ def yadcf_multi_select(expr, value):
'yadcf_multi_select': yadcf_multi_select,
'yadcf_range_number': yadcf_range_number,
'yadcf_range_number_slider': yadcf_range_number,
'yadcf_range_date': yadcf_range_date
'yadcf_range_date': yadcf_range_date,
'regex': lambda expr, value, op: expr.op(op)(value)
}


Expand Down Expand Up @@ -335,7 +336,10 @@ def _set_column_filter_expressions(self):
'columns[{:d}][search][value]'.format(i), '')
if value:
search_func = search_methods[self.columns[i].search_method]
filter_expr = search_func(self.columns[i].sqla_expr, value)
if self.columns[i].search_method == 'regex':
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this extra if/else statement here, but see that it is done because in this case the query method is is actually a function of the dialect. Perhaps the code needs to be reorganized a bit to allow for this, but the current implementation is a bit hacky

filter_expr = search_func(self.columns[i].sqla_expr, value, self._get_regex_operator())
else:
filter_expr = search_func(self.columns[i].sqla_expr, value)
self.filter_expressions.append(filter_expr)

def _set_global_filter_expression(self):
Expand All @@ -348,7 +352,6 @@ def _set_global_filter_expression(self):
self.params.get('search[regex]') == 'true'):
op = self._get_regex_operator()
val = clean_regex(global_search)

def filter_for(col):
return col.sqla_expr.op(op)(val)
else:
Expand Down Expand Up @@ -398,7 +401,7 @@ def _get_regex_operator(self):
if isinstance(
self.query.session.bind.dialect,
postgresql.dialect):
return '~'
return '~*'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please document and motivate the change from case sensitive to case insensitive searching. And perhaps even better to leave as it is, as I understand the MySQL REGEXP operator is case sensitive as well. Perhaps regexpi functions should also be added?

elif isinstance(
self.query.session.bind.dialect,
mysql.dialect):
Expand Down
12 changes: 11 additions & 1 deletion tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
import faker
import unittest
import itertools
import re

from sqlalchemy import create_engine
from sqlalchemy import create_engine, event
from sqlalchemy.orm import sessionmaker
from datetime import datetime, timedelta

Expand All @@ -19,6 +20,9 @@ def setUpClass(cls):
"""Set up fake database session before all tests."""
cls.engine = create_engine(
'sqlite:///db.sqlite', echo=False) # echo=True for debug

event.listen(cls.engine, 'begin', BaseTest.add_regex_on_connect)

Base.metadata.create_all(cls.engine)
Session = sessionmaker(bind=cls.engine)
cls.session = Session()
Expand All @@ -27,6 +31,7 @@ def setUpClass(cls):
@classmethod
def tearDownClass(cls):
"""Tear down database session after all tests."""
event.remove(cls.engine, 'begin', BaseTest.add_regex_on_connect)
Base.metadata.drop_all(cls.engine)

@classmethod
Expand Down Expand Up @@ -74,3 +79,8 @@ def create_dt_params(
params['order[%s][%s]' % (i, key)] = str(value)

return params

@classmethod
def add_regex_on_connect(cls, dbapi_con):
"""Add REGEX functionality for sqlite"""
dbapi_con.connection.create_function("REGEXP", 2, lambda expr, item : re.search(expr, str(item)) is not None)
4 changes: 2 additions & 2 deletions tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class User(Base):
__tablename__ = 'users'

id = Column(Integer, primary_key=True)
name = Column(String, unique=True)
name = Column(String)
created_at = Column(DateTime, default=datetime.datetime.utcnow)
birthday = Column(Date)
address = relationship('Address', uselist=False, backref=backref('user'))
Expand Down Expand Up @@ -48,7 +48,7 @@ class Address(Base):
__tablename__ = 'addresses'

id = Column(Integer, primary_key=True)
description = Column(String, unique=True)
description = Column(String)
user_id = Column(Integer, ForeignKey('users.id'))

def __unicode__(self):
Expand Down
14 changes: 5 additions & 9 deletions tests/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def test_fields_search_filters(self):
params['columns[0][search][value]'] = '=4'
params['columns[1][search][value]'] = user.name
params['columns[2][search][value]'] = '>1965-02-02'
params['columns[2][search][value]'] = '<=99'
params['columns[3][search][value]'] = '<=99'
rowTable = DataTables(params, query, columns)
res = rowTable.output_result()

Expand Down Expand Up @@ -141,14 +141,10 @@ def test_fields_global_search_filtering_with_regex(self):
params, query, columns, allow_regex_searches=True)
res = rowTable.output_result()

if 'error' in res:
# unfortunately sqlite doesn't support regexp out of the box'
assert 'no such function: REGEXP' in res['error']
else:
assert len(res['data']) == 1
assert res['recordsTotal'] == '1'
assert res['recordsFiltered'] == '1'
assert res['data'][0]['1'] == 'Feeeeear Of'
assert len(res['data']) == 1
assert res['recordsTotal'] == '52'
assert res['recordsFiltered'] == '1'
assert res['data'][0]['1'] == 'Feeeeear Of'


class FieldsTest4(BaseTest):
Expand Down
8 changes: 8 additions & 0 deletions tests/test_searching.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ def test_method_none(self):
search_value='abc')
assert res['recordsFiltered'] == '50'

def test_method_regex(self):
res = self.get_result(
column=User.name,
search_method='regex',
search_value='.*')

assert res['recordsFiltered'] == '50'

def test_method_numeric(self):
res = self.get_result(
column=User.id,
Expand Down