db: Add a Genre Table
This table allows us to work with Genre playlists. Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
parent
aeeee1417a
commit
2dc5d9ed0a
|
@ -6,6 +6,7 @@ from typing import Generator
|
|||
from . import albums
|
||||
from . import artists
|
||||
from . import connection
|
||||
from . import genres
|
||||
from . import playlist
|
||||
from . import media
|
||||
from . import playlists
|
||||
|
@ -35,6 +36,7 @@ class Connection(connection.Connection):
|
|||
self.artists = artists.Table(self)
|
||||
self.albums = albums.Table(self, queue=self.artists.queue)
|
||||
self.media = media.Table(self, queue=self.artists.queue)
|
||||
self.genres = genres.Table(self)
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close the database connection."""
|
||||
|
@ -57,7 +59,8 @@ class Connection(connection.Connection):
|
|||
|
||||
def playlist_tables(self) -> Generator[playlist.Table, None, None]:
|
||||
"""Iterate over each playlist table."""
|
||||
for tbl in [self.playlists, self.artists, self.albums, self.media]:
|
||||
for tbl in [self.playlists, self.artists, self.albums, self.media,
|
||||
self.genres]:
|
||||
yield tbl
|
||||
|
||||
def set_active_playlist(self, plist: playlist.Playlist) -> None:
|
||||
|
|
|
@ -210,6 +210,38 @@ CREATE VIEW album_artist_view AS
|
|||
LEFT JOIN media USING (albumid);
|
||||
|
||||
|
||||
/************************
|
||||
* *
|
||||
* Genres *
|
||||
* *
|
||||
************************/
|
||||
|
||||
CREATE TABLE genres (
|
||||
genreid INTEGER PRIMARY KEY,
|
||||
propertyid INTEGER REFERENCES playlist_properties (propertyid)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE,
|
||||
name TEXT NOT NULL UNIQUE COLLATE NOCASE
|
||||
);
|
||||
|
||||
CREATE VIEW genres_view AS
|
||||
SELECT genreid, propertyid, name, active
|
||||
FROM genres
|
||||
JOIN playlist_properties USING (propertyid);
|
||||
|
||||
CREATE TRIGGER genres_insert_trigger AFTER INSERT ON genres
|
||||
BEGIN
|
||||
INSERT INTO playlist_properties (active) VALUES (False);
|
||||
UPDATE genres SET propertyid = last_insert_rowid()
|
||||
WHERE genreid = NEW.genreid;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER genres_delete_trigger AFTER DELETE ON genres
|
||||
BEGIN
|
||||
DELETE FROM playlist_properties WHERE propertyid = OLD.propertyid;
|
||||
END;
|
||||
|
||||
|
||||
/******************************************
|
||||
* *
|
||||
* Create Default Playlists *
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
# Copyright 2022 (c) Anna Schumaker
|
||||
"""A custom Gio.ListModel for genres."""
|
||||
import sqlite3
|
||||
from gi.repository import GObject
|
||||
from .. import format
|
||||
from . import playlist
|
||||
|
||||
|
||||
class Genre(playlist.Playlist):
|
||||
"""Our custom Genre object representing a single genre."""
|
||||
|
||||
genreid = GObject.Property(type=int)
|
||||
|
||||
@property
|
||||
def primary_key(self) -> int:
|
||||
"""Get this Gener's primary key."""
|
||||
return self.genreid
|
||||
|
||||
|
||||
class Table(playlist.Table):
|
||||
"""Our Genre Table."""
|
||||
|
||||
def do_construct(self, **kwargs) -> Genre:
|
||||
"""Construct a new Genre."""
|
||||
return Genre(**kwargs)
|
||||
|
||||
def do_get_sort_key(self, genre: Genre) -> tuple[tuple[str], int]:
|
||||
"""Get a sort key for the Genre."""
|
||||
return (format.sort_key(genre.name), genre.genreid)
|
||||
|
||||
def do_sql_delete(self, genre: Genre) -> sqlite3.Cursor:
|
||||
"""Delete a genre."""
|
||||
return self.sql("DELETE FROM genres WHERE genreid=?", genre.genreid)
|
||||
|
||||
def do_sql_glob(self, glob: str) -> sqlite3.Cursor:
|
||||
"""Search for genres matching the search text."""
|
||||
return self.sql("""SELECT genreid FROM genres
|
||||
WHERE CASEFOLD(name) GLOB ?""", glob)
|
||||
|
||||
def do_sql_insert(self, name: str) -> sqlite3.Cursor | None:
|
||||
"""Create a new genre."""
|
||||
if cur := self.sql("INSERT INTO genres (name) VALUES (?)", name):
|
||||
return self.sql("SELECT * FROM genres_view WHERE genreid=?",
|
||||
cur.lastrowid)
|
||||
|
||||
def do_sql_select_all(self) -> sqlite3.Cursor:
|
||||
"""Load genres from the database."""
|
||||
return self.sql("SELECT * FROM genres_view")
|
||||
|
||||
def do_sql_select_one(self, name: str) -> sqlite3.Cursor:
|
||||
"""Look up a genre by name."""
|
||||
return self.sql("SELECT genreid FROM genres WHERE CASEFOLD(name)=?",
|
||||
name.casefold())
|
||||
|
||||
def do_sql_update(self, genre: playlist.Playlist,
|
||||
column: str, newval) -> sqlite3.Cursor:
|
||||
"""Update a genre."""
|
||||
return self.sql(f"UPDATE genres SET {column}=? WHERE genreid=?",
|
||||
newval, genre.genreid)
|
|
@ -44,13 +44,15 @@ class TestConnection(tests.util.TestCase):
|
|||
self.assertIsInstance(self.sql.artists, emmental.db.artists.Table)
|
||||
self.assertIsInstance(self.sql.albums, emmental.db.albums.Table)
|
||||
self.assertIsInstance(self.sql.media, emmental.db.media.Table)
|
||||
self.assertIsInstance(self.sql.genres, emmental.db.genres.Table)
|
||||
|
||||
self.assertEqual(self.sql.albums.queue, self.sql.artists.queue)
|
||||
self.assertEqual(self.sql.media.queue, self.sql.artists.queue)
|
||||
|
||||
self.assertListEqual([tbl for tbl in self.sql.playlist_tables()],
|
||||
[self.sql.playlists, self.sql.artists,
|
||||
self.sql.albums, self.sql.media])
|
||||
self.sql.albums, self.sql.media,
|
||||
self.sql.genres])
|
||||
|
||||
def test_load(self):
|
||||
"""Check that calling load() loads the tables."""
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
# Copyright 2022 (c) Anna Schumaker
|
||||
"""Tests our genre Gio.ListModel."""
|
||||
import emmental.db
|
||||
import tests.util
|
||||
|
||||
|
||||
class TestGenreObject(tests.util.TestCase):
|
||||
"""Tests our genre object."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
tests.util.TestCase.setUp(self)
|
||||
self.table = self.sql.genres
|
||||
self.genre = emmental.db.genres.Genre(table=self.table, genreid=123,
|
||||
propertyid=456, name="Genre")
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the Genre is set up properly."""
|
||||
self.assertIsInstance(self.genre, emmental.db.playlist.Playlist)
|
||||
self.assertEqual(self.genre.table, self.table)
|
||||
self.assertEqual(self.genre.propertyid, 456)
|
||||
self.assertEqual(self.genre.genreid, 123)
|
||||
self.assertEqual(self.genre.primary_key, 123)
|
||||
self.assertIsNone(self.genre.parent)
|
||||
|
||||
|
||||
class TestGenreTable(tests.util.TestCase):
|
||||
"""Tests our genre table."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up common variables."""
|
||||
tests.util.TestCase.setUp(self)
|
||||
self.table = self.sql.genres
|
||||
|
||||
def test_init(self):
|
||||
"""Test that the genre model is configured correctly."""
|
||||
self.assertIsInstance(self.table, emmental.db.playlist.Table)
|
||||
self.assertEqual(len(self.table), 0)
|
||||
|
||||
def test_construct(self):
|
||||
"""Test constructing a new genre playlist."""
|
||||
genre = self.table.construct(genreid=1, propertyid=1, name="Genre")
|
||||
self.assertIsInstance(genre, emmental.db.genres.Genre)
|
||||
self.assertEqual(genre.table, self.table)
|
||||
self.assertEqual(genre.propertyid, 1)
|
||||
self.assertEqual(genre.genreid, 1)
|
||||
self.assertEqual(genre.name, "Genre")
|
||||
self.assertFalse(genre.active)
|
||||
|
||||
def test_create(self):
|
||||
"""Test creating a genre playlist."""
|
||||
genre = self.table.create("Test Genre")
|
||||
self.assertIsInstance(genre, emmental.db.genres.Genre)
|
||||
self.assertEqual(genre.name, "Test Genre")
|
||||
|
||||
cur = self.sql("SELECT COUNT(name) FROM genres")
|
||||
self.assertEqual(cur.fetchone()["COUNT(name)"], 1)
|
||||
|
||||
row = self.sql("""SELECT COUNT(*) FROM playlist_properties
|
||||
WHERE propertyid=?""", genre.propertyid).fetchone()
|
||||
self.assertEqual(row["COUNT(*)"], 1)
|
||||
|
||||
self.assertIsNone(self.table.create("Test Genre"))
|
||||
|
||||
def test_delete(self):
|
||||
"""Test deleting a genre playlist."""
|
||||
genre = self.table.create("Test Genre")
|
||||
self.assertTrue(genre.delete())
|
||||
self.assertIsNone(self.table.index(genre))
|
||||
|
||||
cur = self.sql("SELECT COUNT(name) FROM genres")
|
||||
self.assertEqual(cur.fetchone()["COUNT(name)"], 0)
|
||||
self.assertEqual(len(self.table), 0)
|
||||
self.assertIsNone(self.table.get_item(0))
|
||||
|
||||
row = self.sql("""SELECT COUNT(*) FROM playlist_properties
|
||||
WHERE propertyid=?""", genre.propertyid).fetchone()
|
||||
self.assertEqual(row["COUNT(*)"], 0)
|
||||
|
||||
self.assertFalse(genre.delete())
|
||||
|
||||
def test_filter(self):
|
||||
"""Test filtering a genre playlist."""
|
||||
self.table.create("Genre 1")
|
||||
self.table.create("Genre 2")
|
||||
|
||||
self.table.filter("*1", now=True)
|
||||
self.assertSetEqual(self.table.get_filter().keys, {1})
|
||||
self.table.filter("genre*", now=True)
|
||||
self.assertSetEqual(self.table.get_filter().keys, {1, 2})
|
||||
|
||||
def test_get_sort_key(self):
|
||||
"""Test the get_sort_key() function."""
|
||||
genre = self.table.create("Genre 1")
|
||||
self.assertTupleEqual(self.table.get_sort_key(genre),
|
||||
(("genre", "1"), genre.genreid))
|
||||
|
||||
def test_load(self):
|
||||
"""Test loading genres from the database."""
|
||||
self.table.create("Genre 1")
|
||||
self.table.create("Genre 2")
|
||||
|
||||
genres2 = emmental.db.genres.Table(self.sql)
|
||||
self.assertEqual(len(genres2), 0)
|
||||
|
||||
genres2.load(now=True)
|
||||
self.assertEqual(len(genres2), 2)
|
||||
self.assertEqual(genres2.get_item(0).name, "Genre 1")
|
||||
self.assertEqual(genres2.get_item(1).name, "Genre 2")
|
||||
|
||||
def test_lookup(self):
|
||||
"""Test looking up genre playlists."""
|
||||
genre = self.table.create("Test Genre")
|
||||
self.assertEqual(self.table.lookup("Test Genre"), genre)
|
||||
self.assertEqual(self.table.lookup("test genre"), genre)
|
||||
self.assertIsNone(self.table.lookup("No Genre"))
|
||||
|
||||
def test_update(self):
|
||||
"""Test updating genre attributes."""
|
||||
genre = self.table.create("Test Genre")
|
||||
genre.active = True
|
||||
|
||||
row = self.sql("""SELECT active FROM playlist_properties
|
||||
WHERE propertyid=?""", genre.propertyid).fetchone()
|
||||
self.assertEqual(row["active"], True)
|
Loading…
Reference in New Issue