db: Create a database row Filter
This filter takes a set of primary keys for rows that should be visible during filtering. Passing None as a value means that all rows are shown. It also has functions for adding or removing individual rows from the filter. Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
parent
2eef68f76f
commit
651f24672b
|
@ -2,6 +2,7 @@
|
|||
"""Base classes for database objects."""
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gio
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class Row(GObject.GObject):
|
||||
|
@ -31,3 +32,76 @@ class Row(GObject.GObject):
|
|||
def primary_key(self) -> None:
|
||||
"""Get the primary key for this row."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Filter(Gtk.Filter):
|
||||
"""A Filter that can be used to search playlists."""
|
||||
|
||||
n_keys = GObject.Property(type=int)
|
||||
|
||||
def __init__(self, keys: set | None = None, **kwargs):
|
||||
"""Set up our Filter."""
|
||||
super().__init__(**kwargs)
|
||||
self._keys = keys
|
||||
self.n_keys = len(keys) if keys is not None else -1
|
||||
|
||||
def __sub__(self, rhs: Gtk.Filter) -> set[int]:
|
||||
"""Subtract two Filters and return the result."""
|
||||
match (self._keys, rhs._keys):
|
||||
case (None, _): return None
|
||||
case (_, None): return self._keys
|
||||
case (_, _): return self._keys - rhs._keys
|
||||
|
||||
def __find_change(self, keys: set[any] | None) -> Gtk.FilterChange | None:
|
||||
if keys == self._keys:
|
||||
return None
|
||||
elif keys is None:
|
||||
return Gtk.FilterChange.LESS_STRICT
|
||||
elif self._keys is None:
|
||||
return Gtk.FilterChange.MORE_STRICT
|
||||
elif keys.issuperset(self._keys):
|
||||
return Gtk.FilterChange.LESS_STRICT
|
||||
elif keys.issubset(self._keys):
|
||||
return Gtk.FilterChange.MORE_STRICT
|
||||
return Gtk.FilterChange.DIFFERENT
|
||||
|
||||
def changed(self, how: Gtk.FilterChange) -> None:
|
||||
"""Notify that the filter has changed."""
|
||||
self.n_keys = len(self._keys) if self._keys is not None else -1
|
||||
super().changed(how)
|
||||
|
||||
def do_get_strictness(self) -> Gtk.FilterMatch:
|
||||
"""Get the strictness of the filter."""
|
||||
if self._keys is None:
|
||||
return Gtk.FilterMatch.ALL
|
||||
if len(self._keys) == 0:
|
||||
return Gtk.FilterMatch.NONE
|
||||
return Gtk.FilterMatch.SOME
|
||||
|
||||
def do_match(self, row: Row) -> bool:
|
||||
"""Check if the Row matches the filter."""
|
||||
return self._keys is None or row.primary_key in self._keys
|
||||
|
||||
def add_row(self, row: Row) -> None:
|
||||
"""Add a Row to the Filter."""
|
||||
if self._keys is not None:
|
||||
self._keys.add(row.primary_key)
|
||||
self.changed(Gtk.FilterChange.LESS_STRICT)
|
||||
|
||||
def remove_row(self, row: Row) -> None:
|
||||
"""Remove a Row from the Filter."""
|
||||
if self._keys is not None:
|
||||
self._keys.discard(row.primary_key)
|
||||
self.changed(Gtk.FilterChange.MORE_STRICT)
|
||||
|
||||
@property
|
||||
def keys(self) -> set[any]:
|
||||
"""Return the set of matching primary keys."""
|
||||
return self._keys
|
||||
|
||||
@keys.setter
|
||||
def keys(self, keys: set[any] | None) -> None:
|
||||
"""Set the matching primary keys."""
|
||||
if (how := self.__find_change(keys)) is not None:
|
||||
self._keys = keys
|
||||
self.changed(how)
|
||||
|
|
|
@ -6,6 +6,7 @@ import emmental.db.table
|
|||
import tests.util.table
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gio
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class TestRow(unittest.TestCase):
|
||||
|
@ -45,3 +46,119 @@ class TestRow(unittest.TestCase):
|
|||
"""Test deleting a Row."""
|
||||
self.assertTrue(self.row.delete())
|
||||
self.table.delete.assert_called_with(self.row)
|
||||
|
||||
|
||||
@unittest.mock.patch("gi.repository.Gtk.Filter.changed")
|
||||
class TestFilter(unittest.TestCase):
|
||||
"""Tests our database row Filter."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
self.filter = emmental.db.table.Filter()
|
||||
self.table = Gio.ListStore()
|
||||
self.row1 = tests.util.table.MockRow(number=1, table=self.table)
|
||||
self.row2 = tests.util.table.MockRow(number=2, table=self.table)
|
||||
|
||||
def test_init(self, mock_changed: unittest.mock.Mock):
|
||||
"""Test that the filter is created correctly."""
|
||||
self.assertIsInstance(self.filter, Gtk.Filter)
|
||||
self.assertIsNone(self.filter._keys, None)
|
||||
self.assertEqual(self.filter.n_keys, -1)
|
||||
|
||||
filter2 = emmental.db.table.Filter(keys={1, 2, 3})
|
||||
self.assertSetEqual(filter2._keys, {1, 2, 3})
|
||||
self.assertEqual(filter2.n_keys, 3)
|
||||
|
||||
def test_subtract(self, mock_changed: unittest.mock.Mock):
|
||||
"""Test subtracting two filters."""
|
||||
filter2 = emmental.db.table.Filter(keys={2, 3})
|
||||
self.assertIsNone(self.filter - self.filter)
|
||||
self.assertIsNone(self.filter - filter2)
|
||||
self.assertSetEqual(filter2 - self.filter, {2, 3})
|
||||
|
||||
self.filter.keys = {1, 2, 3, 4, 5}
|
||||
self.assertSetEqual(self.filter - filter2, {1, 4, 5})
|
||||
self.assertSetEqual(filter2 - self.filter, set())
|
||||
|
||||
def test_strictness(self, mock_changed: unittest.mock.Mock):
|
||||
"""Test checking strictness."""
|
||||
self.assertEqual(self.filter.get_strictness(), Gtk.FilterMatch.ALL)
|
||||
self.filter._keys = set()
|
||||
self.assertEqual(self.filter.get_strictness(), Gtk.FilterMatch.NONE)
|
||||
self.filter._keys = {1, 2, 3}
|
||||
self.assertEqual(self.filter.get_strictness(), Gtk.FilterMatch.SOME)
|
||||
|
||||
def test_add_row(self, mock_changed: unittest.mock.Mock):
|
||||
"""Test adding Rows to the filter."""
|
||||
self.filter.add_row(self.row1)
|
||||
self.assertIsNone(self.filter.keys)
|
||||
|
||||
self.filter.keys = set()
|
||||
self.filter.add_row(self.row1)
|
||||
self.assertSetEqual(self.filter.keys, {1})
|
||||
mock_changed.assert_called_with(Gtk.FilterChange.LESS_STRICT)
|
||||
self.assertEqual(self.filter.n_keys, 1)
|
||||
|
||||
self.filter.add_row(self.row2)
|
||||
self.assertSetEqual(self.filter.keys, {1, 2})
|
||||
mock_changed.assert_called_with(Gtk.FilterChange.LESS_STRICT)
|
||||
self.assertEqual(self.filter.n_keys, 2)
|
||||
|
||||
def test_remove_row(self, mock_changed: unittest.mock.Mock):
|
||||
"""Test removing Rows from the filter."""
|
||||
self.filter.remove_row(self.row1)
|
||||
mock_changed.assert_not_called()
|
||||
|
||||
self.filter.keys = {1, 2}
|
||||
self.filter.remove_row(self.row1)
|
||||
self.assertSetEqual(self.filter._keys, {2})
|
||||
mock_changed.assert_called_with(Gtk.FilterChange.MORE_STRICT)
|
||||
self.assertEqual(self.filter.n_keys, 1)
|
||||
|
||||
mock_changed.reset_mock()
|
||||
self.filter.remove_row(self.row2)
|
||||
self.assertSetEqual(self.filter._keys, set())
|
||||
mock_changed.assert_called_with(Gtk.FilterChange.MORE_STRICT)
|
||||
self.assertEqual(self.filter.n_keys, 0)
|
||||
|
||||
def test_keys(self, mock_changed: unittest.mock.Mock):
|
||||
"""Test setting and getting the filter keys property."""
|
||||
self.assertIsNone(self.filter.keys)
|
||||
|
||||
self.filter.keys = {1, 2, 3}
|
||||
self.assertSetEqual(self.filter._keys, {1, 2, 3})
|
||||
mock_changed.assert_called_with(Gtk.FilterChange.MORE_STRICT)
|
||||
self.assertEqual(self.filter.n_keys, 3)
|
||||
|
||||
mock_changed.reset_mock()
|
||||
self.filter.keys = {1, 2}
|
||||
self.assertSetEqual(self.filter.keys, {1, 2})
|
||||
mock_changed.assert_called_with(Gtk.FilterChange.MORE_STRICT)
|
||||
self.assertEqual(self.filter.n_keys, 2)
|
||||
|
||||
mock_changed.reset_mock()
|
||||
self.filter.keys = {1, 2}
|
||||
mock_changed.assert_not_called()
|
||||
|
||||
self.filter.keys = {1, 2, 3}
|
||||
self.assertSetEqual(self.filter.keys, {1, 2, 3})
|
||||
mock_changed.assert_called_with(Gtk.FilterChange.LESS_STRICT)
|
||||
|
||||
self.filter.keys = {4, 5, 6}
|
||||
self.assertSetEqual(self.filter._keys, {4, 5, 6})
|
||||
mock_changed.assert_called_with(Gtk.FilterChange.DIFFERENT)
|
||||
|
||||
self.filter.keys = None
|
||||
self.assertIsNone(self.filter._keys)
|
||||
mock_changed.assert_called_with(Gtk.FilterChange.LESS_STRICT)
|
||||
self.assertEqual(self.filter.n_keys, -1)
|
||||
|
||||
def test_match(self, mock_changed: unittest.mock.Mock):
|
||||
"""Test matching playlists."""
|
||||
self.assertTrue(self.filter.match(self.row1))
|
||||
self.filter.keys = {1, 2, 3}
|
||||
self.assertTrue(self.filter.match(self.row1))
|
||||
self.filter.keys = {4, 5, 6}
|
||||
self.assertFalse(self.filter.match(self.row1))
|
||||
self.filter.keys = set()
|
||||
self.assertFalse(self.filter.match(self.row1))
|
||||
|
|
Loading…
Reference in New Issue