diff --git a/openlp/core/lib/db.py b/openlp/core/lib/db.py index 41b445cd5..93cd293a5 100644 --- a/openlp/core/lib/db.py +++ b/openlp/core/lib/db.py @@ -31,9 +31,9 @@ import logging import os from PyQt4 import QtCore -from sqlalchemy import create_engine, MetaData +from sqlalchemy import Table, MetaData, Column, types, create_engine from sqlalchemy.exc import InvalidRequestError -from sqlalchemy.orm import scoped_session, sessionmaker +from sqlalchemy.orm import scoped_session, sessionmaker, mapper from sqlalchemy.pool import NullPool from openlp.core.utils import AppLocation, delete_file @@ -59,6 +59,42 @@ def init_db(url, auto_flush=True, auto_commit=False): autocommit=auto_commit, bind=engine)) return session, metadata + +def upgrade_db(url, upgrade): + """ + Upgrade a database. + + ``url`` + The url of the database to upgrade. + + ``upgrade`` + The python module that contains the upgrade instructions. + """ + session, metadata = init_db(url) + tables = upgrade.upgrade_setup(metadata) + metadata_table = Table(u'metadata', metadata, + Column(u'key', types.Unicode(64), primary_key=True), + Column(u'value', types.UnicodeText(), default=None) + ) + metadata_table.create(checkfirst=True) + mapper(Metadata, metadata_table) + version_meta = session.query(Metadata).get(u'version') + if version_meta is None: + version_meta = Metadata.populate(key=u'version', value=u'0') + version = 0 + else: + version = int(version_meta.value) + version += 1 + while hasattr(upgrade, u'upgrade_%d' % version): + log.debug(u'Running upgrade_%d', version) + do_upgrade = getattr(upgrade, u'upgrade_%d' % version) + if not do_upgrade(session, metadata, tables): + break + version += 1 + version_meta.value = unicode(version) + session.add(version_meta) + session.commit() + def delete_database(plugin_name, db_file_name=None): """ Remove a database file from the system. @@ -79,6 +115,7 @@ def delete_database(plugin_name, db_file_name=None): AppLocation.get_section_data_path(plugin_name), plugin_name) return delete_file(db_file_path) + class BaseModel(object): """ BaseModel provides a base object with a set of generic functions @@ -94,11 +131,18 @@ class BaseModel(object): return instance +class Metadata(BaseModel): + """ + Provides a class for the metadata table. + """ + pass + + class Manager(object): """ Provide generic object persistence management """ - def __init__(self, plugin_name, init_schema, db_file_name=None): + def __init__(self, plugin_name, init_schema, db_file_name=None, upgrade_schema=None): """ Runs the initialisation process that includes creating the connection to the database and the tables if they don't exist. @@ -109,6 +153,9 @@ class Manager(object): ``init_schema`` The init_schema function for this database + ``upgrade_schema`` + The upgrade_schema function for this database + ``db_file_name`` The file name to use for this database. Defaults to None resulting in the plugin_name being used. @@ -134,6 +181,8 @@ class Manager(object): unicode(settings.value(u'db hostname').toString()), unicode(settings.value(u'db database').toString())) settings.endGroup() + if upgrade_schema: + upgrade_schema(self.db_url) self.session = init_schema(self.db_url) def save_object(self, object_instance, commit=True): diff --git a/openlp/plugins/songs/lib/db.py b/openlp/plugins/songs/lib/db.py index c5c019c3c..f37aed29f 100644 --- a/openlp/plugins/songs/lib/db.py +++ b/openlp/plugins/songs/lib/db.py @@ -32,7 +32,8 @@ the Songs plugin from sqlalchemy import Column, ForeignKey, Table, types from sqlalchemy.orm import mapper, relation -from openlp.core.lib.db import BaseModel, init_db +from openlp.core.lib.db import BaseModel, init_db, upgrade_db +from openlp.plugins.songs.lib import upgrade class Author(BaseModel): """ @@ -70,6 +71,14 @@ class Topic(BaseModel): """ pass +def upgrade_schema(url): + """ + Upgrades the songs database. + + ``url`` + The database to upgrade + """ + upgrade_db(url, upgrade) def init_schema(url): """ @@ -111,10 +120,6 @@ def init_schema(url): * file_name * type - **media_files_songs Table** - * media_file_id - * song_id - **song_books Table** The *song_books* table holds a list of books that a congregation gets their songs from, or old hymnals now no longer used. This table has the @@ -162,7 +167,7 @@ def init_schema(url): # Definition of the "authors" table authors_table = Table(u'authors', metadata, - Column(u'id', types.Integer, primary_key=True), + Column(u'id', types.Integer(), primary_key=True), Column(u'first_name', types.Unicode(128)), Column(u'last_name', types.Unicode(128)), Column(u'display_name', types.Unicode(255), index=True, nullable=False) @@ -170,22 +175,25 @@ def init_schema(url): # Definition of the "media_files" table media_files_table = Table(u'media_files', metadata, - Column(u'id', types.Integer, primary_key=True), + Column(u'id', types.Integer(), primary_key=True), + Column(u'song_id', types.Integer(), ForeignKey(u'songs.id'), + default=None), Column(u'file_name', types.Unicode(255), nullable=False), - Column(u'type', types.Unicode(64), nullable=False, default=u'audio') + Column(u'type', types.Unicode(64), nullable=False, default=u'audio'), + Column(u'weight', types.Integer(), default=0) ) # Definition of the "song_books" table song_books_table = Table(u'song_books', metadata, - Column(u'id', types.Integer, primary_key=True), + Column(u'id', types.Integer(), primary_key=True), Column(u'name', types.Unicode(128), nullable=False), Column(u'publisher', types.Unicode(128)) ) # Definition of the "songs" table songs_table = Table(u'songs', metadata, - Column(u'id', types.Integer, primary_key=True), - Column(u'song_book_id', types.Integer, + Column(u'id', types.Integer(), primary_key=True), + Column(u'song_book_id', types.Integer(), ForeignKey(u'song_books.id'), default=None), Column(u'title', types.Unicode(255), nullable=False), Column(u'alternate_title', types.Unicode(255)), @@ -202,31 +210,23 @@ def init_schema(url): # Definition of the "topics" table topics_table = Table(u'topics', metadata, - Column(u'id', types.Integer, primary_key=True), + Column(u'id', types.Integer(), primary_key=True), Column(u'name', types.Unicode(128), index=True, nullable=False) ) # Definition of the "authors_songs" table authors_songs_table = Table(u'authors_songs', metadata, - Column(u'author_id', types.Integer, + Column(u'author_id', types.Integer(), ForeignKey(u'authors.id'), primary_key=True), - Column(u'song_id', types.Integer, - ForeignKey(u'songs.id'), primary_key=True) - ) - - # Definition of the "media_files_songs" table - media_files_songs_table = Table(u'media_files_songs', metadata, - Column(u'media_file_id', types.Integer, - ForeignKey(u'media_files.id'), primary_key=True), - Column(u'song_id', types.Integer, + Column(u'song_id', types.Integer(), ForeignKey(u'songs.id'), primary_key=True) ) # Definition of the "songs_topics" table songs_topics_table = Table(u'songs_topics', metadata, - Column(u'song_id', types.Integer, + Column(u'song_id', types.Integer(), ForeignKey(u'songs.id'), primary_key=True), - Column(u'topic_id', types.Integer, + Column(u'topic_id', types.Integer(), ForeignKey(u'topics.id'), primary_key=True) ) @@ -238,8 +238,7 @@ def init_schema(url): 'authors': relation(Author, backref='songs', secondary=authors_songs_table, lazy=False), 'book': relation(Book, backref='songs'), - 'media_files': relation(MediaFile, backref='songs', - secondary=media_files_songs_table), + 'media_files': relation(MediaFile, backref='songs'), 'topics': relation(Topic, backref='songs', secondary=songs_topics_table) }) diff --git a/openlp/plugins/songs/lib/upgrade.py b/openlp/plugins/songs/lib/upgrade.py new file mode 100644 index 000000000..09b7cc2e0 --- /dev/null +++ b/openlp/plugins/songs/lib/upgrade.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2011 Raoul Snyman # +# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan, # +# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias # +# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +""" +The :mod:`upgrade` module provides a way for the database and schema that is the backend for +the Songs plugin +""" + +from sqlalchemy import Column, ForeignKey, Table, types +from migrate import changeset +from migrate.changeset.constraint import ForeignKeyConstraint + +def upgrade_setup(metadata): + """ + Set up the latest revision all tables, with reflection, needed for the + upgrade process. If you want to drop a table, you need to remove it from + here, and add it to your upgrade function. + """ + tables = { + u'authors': Table(u'authors', metadata, autoload=True), + u'media_files': Table(u'media_files', metadata, autoload=True), + u'song_books': Table(u'song_books', metadata, autoload=True), + u'songs': Table(u'songs', metadata, autoload=True), + u'topics': Table(u'topics', metadata, autoload=True), + u'authors_songs': Table(u'authors_songs', metadata, autoload=True), + u'songs_topics': Table(u'songs_topics', metadata, autoload=True) + } + return tables + + +def upgrade_1(session, metadata, tables): + Table(u'media_files_songs', metadata, autoload=True).drop(checkfirst=True) + Column(u'song_id', types.Integer(), default=None)\ + .create(table=tables[u'media_files'], populate_default=True) + Column(u'weight', types.Integer(), default=0)\ + .create(table=tables[u'media_files'], populate_default=True) + if metadata.bind.url.get_dialect().name != 'sqlite': + ForeignKeyConstraint([u'song_id'], [u'songs.id'], + table=tables[u'media_files']).create() + return True \ No newline at end of file diff --git a/openlp/plugins/songs/songsplugin.py b/openlp/plugins/songs/songsplugin.py index 8a773be90..9fd113186 100644 --- a/openlp/plugins/songs/songsplugin.py +++ b/openlp/plugins/songs/songsplugin.py @@ -37,7 +37,7 @@ from openlp.core.lib.db import Manager from openlp.core.lib.ui import UiStrings, base_action, icon_action from openlp.core.utils.actions import ActionList from openlp.plugins.songs.lib import clean_song, SongMediaItem, SongsTab -from openlp.plugins.songs.lib.db import init_schema, Song +from openlp.plugins.songs.lib.db import init_schema, upgrade_schema, Song from openlp.plugins.songs.lib.importer import SongFormat from openlp.plugins.songs.lib.olpimport import OpenLPSongImport @@ -58,8 +58,8 @@ class SongsPlugin(Plugin): Create and set up the Songs plugin. """ Plugin.__init__(self, u'songs', plugin_helpers, SongMediaItem, SongsTab) + self.manager = Manager(u'songs', init_schema, upgrade_schema=upgrade_schema) self.weight = -10 - self.manager = Manager(u'songs', init_schema) self.icon_path = u':/plugins/plugin_songs.png' self.icon = build_icon(self.icon_path)