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

feat: add does-not-contain action to the filter #433

Merged
Merged
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
3 changes: 3 additions & 0 deletions languages/ru_RU/LC_MESSAGES/sportorg.po
Original file line number Diff line number Diff line change
Expand Up @@ -1551,6 +1551,9 @@ msgstr "кр."
msgid "contain"
msgstr "содержит"

msgid "doesn't contain"
msgstr "не содержит"

msgid "equal to"
msgstr "равно"

Expand Down
11 changes: 8 additions & 3 deletions sportorg/gui/dialogs/filter_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,14 @@ def __init__(self, table=None):
self.label.setText(headers[i])
self.label.setObjectName('filter_label_' + str(i))
self.label.setMaximumWidth(120)
self.combo_action = AdvComboBox(
self, {translate('contain'), translate('equal to')}, max_width=90
)
actions = [
translate('equal to'),
translate('contain'),
translate("doesn't contain"),
]
default_action = actions[0]
self.combo_action = AdvComboBox(self, actions, max_width=90)
self.combo_action.setCurrentText(default_action)
self.combo_action.setObjectName('filter_action_' + str(i))
self.combo_value = AdvComboBox(self)
self.combo_value.setMinimumWidth(150)
Expand Down
34 changes: 27 additions & 7 deletions sportorg/gui/tabs/memory_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,18 +108,15 @@ def apply_filter(self):
current_array.extend(self.filter_backup)
self.filter_backup.clear()
for column in self.filter.keys():
check_regexp = re.escape(self.filter.get(column)[0])
action = self.filter.get(column)[1]
filter_action = self.filter.get(column)[1]
filter_value = self.filter.get(column)[0]

check = re.compile('.*' + check_regexp + '.*')

if action == translate('equal to'):
check = re.compile(check_regexp + '$')
check = self.compile_regex(filter_action, filter_value)

i = 0
while i < len(current_array):
value = self.get_item(current_array[i], column)
if not check.match(str(value)):
if not self.match_value(check, str(value)):
self.filter_backup.append(current_array.pop(i))
i -= 1
i += 1
Expand All @@ -129,6 +126,29 @@ def apply_filter(self):
self.set_source_array(current_array)
self.init_cache()

@staticmethod
def compile_regex(action: str, raw_value: str) -> re.Pattern:
"""Compiles a regular expression pattern based on filter action filter value.

Args:
action (str): The action to perform (contain, equal to, doesn't contain).
raw_value (str): The filter value to match against.

Returns:
Pattern[str]: The compiled regular expression pattern.
"""
value = re.escape(raw_value)
regex_string = {
translate('contain'): f'.*{value}.*',
translate('equal to'): f'{value}$',
translate("doesn't contain"): f'^((?!{value}).)*$',
}.get(action, '.*')
return re.compile(regex_string)

@staticmethod
def match_value(check: re.Pattern, value: str) -> bool:
return bool(check.match(value))

def apply_search(self):
if not self.search:
return
Expand Down
93 changes: 93 additions & 0 deletions tests/test_memory_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import pytest

from sportorg.gui.tabs.memory_model import AbstractSportOrgMemoryModel
from sportorg.language import translate


@pytest.mark.parametrize(
'pattern, value, expected',
[
('', '', True),
('', 'Ivan', False),
('Ivan', '', False),
('Ivan', 'Ivan', True),
('Ivan', 'Aleksey', False),
('ivan', 'Ivan', False),
('Ivan', 'Ivanov', False),
('Ivan', 'Ivanov Ivan', False),
('Ivan', 'Ivanov Ivan Ivanovich', False),
('Иван', 'Иван', True),
('1993', '1993', True),
('1993', '4651993', False),
],
)
def test_filter_equal_to_action(pattern, value, expected):
model = AbstractSportOrgMemoryModel
check = model.compile_regex(translate('equal to'), pattern)
result = model.match_value(check, str(value))
assert result == expected


@pytest.mark.parametrize(
'pattern, value, expected',
[
('', '', True),
('', 'Ivan', True),
('Ivan', '', False),
('Ivan', 'Ivan', True),
('Ivan', 'Aleksey', False),
('ivan', 'Ivan', False),
('Ivan', 'Ivanov', True),
('Ivan', 'Sidorov Ivan', True),
('Ivan', 'Sidorov Ivan Petrovich', True),
('Иван', 'Иван', True),
('1993', '1993', True),
('1993', '4651993', True),
],
)
def test_filter_contain_to_action(pattern, value, expected):
model = AbstractSportOrgMemoryModel
check = model.compile_regex(translate('contain'), pattern)
result = model.match_value(check, str(value))
assert result == expected


@pytest.mark.parametrize(
'pattern, value, expected',
[
('', '', True),
('', 'Ivan', False),
('Ivan', '', True),
('Ivan', 'Ivan', False),
('Ivan', 'Aleksey', True),
('ivan', 'Ivan', True),
('Ivan', 'Ivanov', False),
('Ivan', 'Sidorov Ivan', False),
('Ivan', 'Sidorov Ivan Petrovich', False),
('Иван', 'Иван', False),
('1993', '1993', False),
('1993', '4651993', False),
],
)
def test_filter_doesnt_contain_to_action(pattern, value, expected):
model = AbstractSportOrgMemoryModel
check = model.compile_regex(translate("doesn't contain"), pattern)
result = model.match_value(check, str(value))
assert result == expected


@pytest.mark.parametrize(
'pattern, value, expected',
[
('', '', True),
('', 'Ivan', True),
('Ivan', '', True),
('Ivan', 'Ivan', True),
('Ivan', 'Aleksey', True),
],
)
def test_filter_wrong_action(pattern, value, expected):
model = AbstractSportOrgMemoryModel
check = model.compile_regex(translate('wrong action'), pattern)
result = model.match_value(check, value)
assert result == expected
Loading