From b7f1a05967344b296b89c9ebf9aca985335fb720 Mon Sep 17 00:00:00 2001 From: Anna Schumaker Date: Wed, 31 Aug 2022 16:17:45 -0400 Subject: [PATCH] db: Give Decades knowledge about their Years Similar to the Artists tree structure. I create a filter on the Year table for each Decade object and adjust filtering so a Decade remains visible if one or more years match the query. Signed-off-by: Anna Schumaker --- emmental/db/decades.py | 27 +++++++++++++++++++++++++- tests/db/test_decades.py | 42 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/emmental/db/decades.py b/emmental/db/decades.py index 68dd514..5cb51de 100644 --- a/emmental/db/decades.py +++ b/emmental/db/decades.py @@ -2,6 +2,8 @@ """A custom Gio.ListModel for working with decades.""" import sqlite3 from gi.repository import GObject +from gi.repository import Gtk +from .years import Year from . import playlist @@ -10,6 +12,19 @@ class Decade(playlist.Playlist): decade = GObject.Property(type=int) + def __init__(self, **kwargs): + """Initialize a Decade object.""" + super().__init__(**kwargs) + self.add_children(self.table.sql.years, + Gtk.CustomFilter.new(self.__match_year)) + + def __match_year(self, year: Year) -> bool: + return self.decade == year.year // 10 * 10 + + def get_years(self) -> list[Year]: + """Get a list of years for this decade.""" + return self.table.get_years(self) + @property def primary_key(self) -> int: """Get the primary key of this Decade.""" @@ -29,12 +44,16 @@ class Table(playlist.Table): def do_sql_delete(self, decade: Decade) -> sqlite3.Cursor: """Delete a decade.""" + for year in decade.get_years(): + year.delete() return self.sql("DELETE FROM decades WHERE decade=?", decade.decade) def do_sql_glob(self, glob: str) -> sqlite3.Cursor: """Search for decades matching the search text.""" return self.sql("""SELECT decade FROM decades_view - WHERE CASEFOLD(name) GLOB ?""", glob) + WHERE CASEFOLD(name) GLOB :glob + UNION SELECT (year / 10 * 10) AS decade + FROM years WHERE year GLOB :glob""", glob=glob) def do_sql_insert(self, year: int) -> sqlite3.Cursor | None: """Create a new Decade playlist.""" @@ -51,3 +70,9 @@ class Table(playlist.Table): """Look up an decade by year.""" return self.sql("SELECT decade FROM decades WHERE decade=?", year // 10 * 10) + + def get_years(self, decade: Decade) -> list[Year]: + """Get the list of years for this decade.""" + rows = self.sql("SELECT year FROM years WHERE (year / 10 * 10)=?", + decade.decade) + return [self.sql.years.rows.get(row["year"]) for row in rows] diff --git a/tests/db/test_decades.py b/tests/db/test_decades.py index ecfef8f..e551775 100644 --- a/tests/db/test_decades.py +++ b/tests/db/test_decades.py @@ -1,7 +1,9 @@ # Copyright 2022 (c) Anna Schumaker """Tests our decade Gio.ListModel.""" +import unittest.mock import emmental.db import tests.util +from gi.repository import Gtk class TestDecadeObject(tests.util.TestCase): @@ -25,6 +27,26 @@ class TestDecadeObject(tests.util.TestCase): self.assertEqual(self.decade.name, "The 2020s") self.assertIsNone(self.decade.parent) + def test_get_years(self): + """Test getting the list of years for this decade.""" + with unittest.mock.patch.object(self.table, "get_years", + return_value=[1, 2, 3]) as mock: + self.assertListEqual(self.decade.get_years(), [1, 2, 3]) + mock.assert_called_with(self.decade) + + def test_years_model(self): + """Test getting a Gio.ListModel representing a Decade's years.""" + self.assertIsInstance(self.decade.children, Gtk.FilterListModel) + self.assertIsInstance(self.decade.children.get_filter(), + Gtk.CustomFilter) + self.assertEqual(self.decade.children.get_model(), self.sql.years) + + year = self.sql.years.create(2023) + self.assertTrue(self.decade.children.get_filter().match(year)) + + year = self.sql.years.create(1988) + self.assertFalse(self.decade.children.get_filter().match(year)) + class TestDecadeTable(tests.util.TestCase): """Tests our decade table.""" @@ -33,6 +55,7 @@ class TestDecadeTable(tests.util.TestCase): """Set up common variables.""" super().setUp() self.table = self.sql.decades + self.sql.years.load() def test_init(self): """Test that the decade table is configured correctly.""" @@ -69,6 +92,7 @@ class TestDecadeTable(tests.util.TestCase): def test_delete(self): """Test deleting a decade playlist.""" decade = self.table.create(1980) + self.sql.years.create(1988) self.assertTrue(decade.delete()) cur = self.sql("SELECT COUNT(decade) FROM decades") @@ -80,6 +104,9 @@ class TestDecadeTable(tests.util.TestCase): WHERE propertyid=?""", decade.propertyid).fetchone() self.assertEqual(row["COUNT(*)"], 0) + row = self.sql("""SELECT COUNT(*) FROM years""").fetchone() + self.assertEqual(row["COUNT(*)"], 0) + self.assertFalse(decade.delete()) def test_filter(self): @@ -87,11 +114,18 @@ class TestDecadeTable(tests.util.TestCase): self.table.create(1980) self.table.create(1990) + self.sql.years.create(1985) + self.sql.years.create(1988) + self.sql.years.create(1992) + self.table.filter("*80*", now=True) self.assertSetEqual(self.table.get_filter().keys, {1980}) self.table.filter("the*s", now=True) self.assertSetEqual(self.table.get_filter().keys, {1980, 1990}) + self.table.filter("1988", now=True) + self.assertSetEqual(self.table.get_filter().keys, {1980}) + def test_get_sort_key(self): """Test getting the sort key for a decade playlist.""" decade = self.table.create(1980) @@ -128,3 +162,11 @@ class TestDecadeTable(tests.util.TestCase): row = self.sql("""SELECT active FROM playlist_properties WHERE propertyid=?""", decade.propertyid).fetchone() self.assertEqual(row["active"], True) + + def test_get_years(self): + """Test getting the list of years for a decade.""" + decade = self.table.create(1980) + y1985 = self.sql.years.create(1985) + y1988 = self.sql.years.create(1988) + + self.assertListEqual(self.table.get_years(decade), [y1985, y1988])