← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~googol-hush/openlp/database-version-control into lp:openlp

 

Andreas Preikschat has proposed merging lp:~googol-hush/openlp/database-version-control into lp:openlp.

Requested reviews:
  OpenLP Core (openlp-core)

For more details, see:
https://code.launchpad.net/~googol-hush/openlp/database-version-control/+merge/64223

Hello,

- added database version control API

**WARNING**
This creates a "meatadata" table in your databases!

**NOTE**
Work is still in progress.
-- 
https://code.launchpad.net/~googol-hush/openlp/database-version-control/+merge/64223
Your team OpenLP Core is requested to review the proposed merge of lp:~googol-hush/openlp/database-version-control into lp:openlp.
=== modified file 'openlp/core/lib/db.py'
--- openlp/core/lib/db.py	2011-05-28 12:50:29 +0000
+++ openlp/core/lib/db.py	2011-06-10 17:19:24 +0000
@@ -31,9 +31,10 @@
 import os
 
 from PyQt4 import QtCore
-from sqlalchemy import create_engine, MetaData
+from sqlalchemy import create_engine, MetaData, Column, Table, types
 from sqlalchemy.exc import InvalidRequestError
-from sqlalchemy.orm import scoped_session, sessionmaker
+from sqlalchemy.orm import class_mapper, scoped_session, mapper, sessionmaker
+from sqlalchemy.orm.exc import UnmappedClassError
 from sqlalchemy.pool import NullPool
 
 from openlp.core.utils import AppLocation, delete_file
@@ -52,11 +53,24 @@
 
     ``auto_commit``
         Sets the commit behaviour of the session
+
+    The database has a **metadata** table. It has two columns, namely a ``key``
+    and a ``value`` column. This table is the place of any data related to the
+    database but not to its content.
     """
     engine = create_engine(url, poolclass=NullPool)
     metadata = MetaData(bind=engine)
     session = scoped_session(sessionmaker(autoflush=auto_flush,
         autocommit=auto_commit, bind=engine))
+    # Create the default metadata table.
+    meta_table = Table(u'metadata', metadata,
+        Column(u'key', types.Unicode(255), primary_key=True, index=True),
+        Column(u'value', types.Unicode(255)),
+    )
+    try:
+        class_mapper(MetaModel)
+    except UnmappedClassError:
+        mapper(MetaModel, meta_table)
     return session, metadata
 
 def delete_database(plugin_name, db_file_name=None):
@@ -94,6 +108,13 @@
         return instance
 
 
+class MetaModel(BaseModel):
+    """
+    Metadata Model
+    """
+    pass
+
+
 class Manager(object):
     """
     Provide generic object persistence management
@@ -136,6 +157,41 @@
         settings.endGroup()
         self.session = init_schema(self.db_url)
 
+    def is_valid_database(self, allowed_version):
+        """
+        Returns ``False`` when the given version is lower than the version found
+        in the database.
+
+        ``allowed_version``
+            The version the database is expected to be.
+        """
+        db_version = self.get_meta(u'dbversion')
+        if db_version is None:
+            return True
+        return allowed_version >= int(db_version.value)
+
+    def get_meta(self, key):
+        """
+        Returns the meta data object for given ``key``.
+        """
+        return self.get_object(MetaModel, key)
+
+    def create_meta(self, key, value):
+        """
+        Utility method to save meta data for this database.
+
+        ``key``
+            The key for this instance.
+
+        ``value``
+            The value for this instance.
+        """
+        if not isinstance(value, unicode):
+            value = unicode(value)
+        obj = MetaModel.populate(key=key, value=value)
+        self.save_object(obj)
+        return obj
+
     def save_object(self, object_instance, commit=True):
         """
         Save an object to the database
@@ -215,10 +271,10 @@
 
         ``filter_clause``
             The filter governing selection of objects to return. Defaults to
-            None.
+            ``None``.
 
         ``order_by_ref``
-            Any parameters to order the returned objects by. Defaults to None.
+            Any parameters to order the returned objects by. Defaults to ``None``.
         """
         query = self.session.query(object_class)
         if filter_clause is not None:
@@ -236,7 +292,7 @@
 
         ``filter_clause``
             The filter governing selection of objects to return. Defaults to
-            None.
+            ``None``.
         """
         query = self.session.query(object_class)
         if filter_clause is not None:

=== modified file 'openlp/core/lib/plugin.py'
--- openlp/core/lib/plugin.py	2011-06-05 18:46:00 +0000
+++ openlp/core/lib/plugin.py	2011-06-10 17:19:24 +0000
@@ -156,6 +156,7 @@
         self.icon = None
         self.media_item_class = media_item_class
         self.settings_tab_class = settings_tab_class
+        self.settings_tab = None
         self.weight = 0
         self.status = PluginStatus.Inactive
         # Set up logging

=== modified file 'openlp/core/ui/pluginform.py'
--- openlp/core/ui/pluginform.py	2011-06-05 18:46:00 +0000
+++ openlp/core/ui/pluginform.py	2011-06-10 17:19:24 +0000
@@ -102,9 +102,9 @@
         self.versionNumberLabel.setText(self.activePlugin.version)
         self.aboutTextBrowser.setHtml(self.activePlugin.about())
         self.programaticChange = True
-        status = 1
+        status = PluginStatus.Active
         if self.activePlugin.status == PluginStatus.Active:
-            status = 0
+            status = PluginStatus.Inactive
         self.statusComboBox.setCurrentIndex(status)
         self.statusComboBox.setEnabled(True)
         self.programaticChange = False
@@ -128,7 +128,7 @@
     def onStatusComboBoxChanged(self, status):
         if self.programaticChange:
             return
-        if status == 0:
+        if status == PluginStatus.Inactive:
             Receiver.send_message(u'cursor_busy')
             self.activePlugin.toggleStatus(PluginStatus.Active)
             Receiver.send_message(u'cursor_normal')

=== modified file 'openlp/plugins/alerts/alertsplugin.py'
--- openlp/plugins/alerts/alertsplugin.py	2011-05-26 17:11:22 +0000
+++ openlp/plugins/alerts/alertsplugin.py	2011-06-10 17:19:24 +0000
@@ -40,7 +40,20 @@
 log = logging.getLogger(__name__)
 
 class AlertsPlugin(Plugin):
+    """
+    This plugin allows to display short messages on the display screen without
+    hiding the text (e. g. lyrics).
+
+    ``CURRENT_DB_VERSION``
+        This class constant indicates the database version number which the
+        alerts database is supposed to have.
+
+        ``2``
+            Prior to 1.9.6 the alerts database did not have a version number.
+            After 1.9.6 the version number was set to ``2``.
+    """
     log.info(u'Alerts Plugin loaded')
+    CURRENT_DB_VERSION = 2
 
     def __init__(self, plugin_helpers):
         Plugin.__init__(self, u'Alerts', plugin_helpers,
@@ -92,6 +105,11 @@
         action_list = ActionList.get_instance()
         action_list.remove_action(self.toolsAlertItem, u'Tools')
 
+    def appStartup(self):
+        db_version = self.manager.get_meta(u'dbversion')
+        if db_version is None:
+            db_version = self.manager.create_meta(u'dbversion', u'2')
+
     def toggleAlertsState(self):
         self.alertsActive = not self.alertsActive
         QtCore.QSettings().setValue(self.settingsSection + u'/active',

=== modified file 'openlp/plugins/bibles/bibleplugin.py'
--- openlp/plugins/bibles/bibleplugin.py	2011-05-26 20:41:19 +0000
+++ openlp/plugins/bibles/bibleplugin.py	2011-06-10 17:19:24 +0000
@@ -38,7 +38,20 @@
 log = logging.getLogger(__name__)
 
 class BiblePlugin(Plugin):
+    """
+    This plugin allows to display bible verses.
+
+    ``CURRENT_DB_VERSION``
+        This class constant indicates the database version number which the
+        bible databases are supposed to have.
+
+        ``2``
+            The database version was initially set to 2. However, there has been
+            a database change (between 1.9.5 and 1.9.6) without increasing the
+            version number.
+    """
     log.info(u'Bible Plugin loaded')
+    CURRENT_DB_VERSION = 2
 
     def __init__(self, plugin_helpers):
         Plugin.__init__(self, u'Bibles', plugin_helpers,
@@ -80,12 +93,13 @@
         """
         Perform tasks on application starup
         """
-        if len(self.manager.old_bible_databases):
-            if QtGui.QMessageBox.information(self.formparent, 
+        # Upgrade pre 1.9.6 bible databases.
+        if self.manager.old_bible_databases:
+            if QtGui.QMessageBox.information(self.formparent,
                 translate('OpenLP', 'Information'), translate('OpenLP',
                 'Bible format has changed.\nYou have to upgrade your '
-                'existing Bibles.\nShould OpenLP upgrade now?'), 
-                QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes | 
+                'existing Bibles.\nShould OpenLP upgrade now?'),
+                QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Yes |
                 QtGui.QMessageBox.No)) == QtGui.QMessageBox.Yes:
                 self.onToolsUpgradeItemTriggered()
 
@@ -130,11 +144,11 @@
         """
         Upgrade older bible databases.
         """
-        if not hasattr(self, u'upgrade_wizard'):
-            self.upgrade_wizard = BibleUpgradeForm(self.formparent, 
+        if not hasattr(self, u'upgradeWizard'):
+            self.upgradeWizard = BibleUpgradeForm(self.formparent,
                 self.manager, self)
         # If the import was not cancelled then reload.
-        if self.upgrade_wizard.exec_():
+        if self.upgradeWizard.exec_():
             self.mediaItem.reloadBibles()
 
     def onBibleImportClick(self):

=== modified file 'openlp/plugins/bibles/forms/bibleupgradeform.py'
--- openlp/plugins/bibles/forms/bibleupgradeform.py	2011-06-05 17:39:13 +0000
+++ openlp/plugins/bibles/forms/bibleupgradeform.py	2011-06-10 17:19:24 +0000
@@ -39,7 +39,7 @@
 from openlp.core.lib.ui import UiStrings, critical_error_message_box
 from openlp.core.ui.wizard import OpenLPWizard, WizardStrings
 from openlp.core.utils import AppLocation, delete_file
-from openlp.plugins.bibles.lib.db import BibleDB, BibleMeta, OldBibleDB,\
+from openlp.plugins.bibles.lib.db import BibleDB, OldBibleDB,\
     BiblesResourcesDB, clean_filename
 from openlp.plugins.bibles.lib.http import BSExtract, BGExtract, CWExtract
 
@@ -70,8 +70,7 @@
         self.mediaItem = bibleplugin.mediaItem
         self.suffix = u'.sqlite'
         self.settingsSection = u'bibles'
-        self.path = AppLocation.get_section_data_path(
-            self.settingsSection)
+        self.path = AppLocation.get_section_data_path(self.settingsSection)
         self.files = self.manager.old_bible_databases
         self.success = {}
         self.newbibles = {}
@@ -676,8 +675,7 @@
                             Receiver.send_message(u'openlp_process_events')
                         self.newbibles[number].session.commit()
             else:
-                language_id = self.newbibles[number].get_object(BibleMeta,
-                    u'language_id')
+                language_id = self.newbibles[number].get_meta(u'language_id')
                 if not language_id:
                     language_id = self.newbibles[number].get_language(name)
                 if not language_id:

=== modified file 'openlp/plugins/bibles/lib/db.py'
--- openlp/plugins/bibles/lib/db.py	2011-06-04 13:52:26 +0000
+++ openlp/plugins/bibles/lib/db.py	2011-06-10 17:19:24 +0000
@@ -43,13 +43,6 @@
 
 log = logging.getLogger(__name__)
 
-class BibleMeta(BaseModel):
-    """
-    Bible Meta Data
-    """
-    pass
-
-
 class Book(BaseModel):
     """
     Song model
@@ -85,11 +78,6 @@
     """
     session, metadata = init_db(url)
 
-    meta_table = Table(u'metadata', metadata,
-        Column(u'key', types.Unicode(255), primary_key=True, index=True),
-        Column(u'value', types.Unicode(255)),
-    )
-
     book_table = Table(u'book', metadata,
         Column(u'id', types.Integer, primary_key=True),
         Column(u'book_reference_id', types.Integer, index=True),
@@ -106,10 +94,6 @@
     )
 
     try:
-        class_mapper(BibleMeta)
-    except UnmappedClassError:
-        mapper(BibleMeta, meta_table)
-    try:
         class_mapper(Book)
     except UnmappedClassError:
         mapper(Book, book_table,
@@ -181,7 +165,7 @@
         """
         Returns the version name of the Bible.
         """
-        version_name = self.get_object(BibleMeta, u'Version')
+        version_name = self.get_meta(u'Version')
         self.name = version_name.value if version_name else None
         return self.name
 
@@ -285,21 +269,6 @@
         self.session.add(verse)
         return verse
 
-    def create_meta(self, key, value):
-        """
-        Utility method to save BibleMeta objects in a Bible database.
-
-        ``key``
-            The key for this instance.
-
-        ``value``
-            The value for this instance.
-        """
-        if not isinstance(value, unicode):
-            value = unicode(value)
-        log.debug(u'BibleDB.save_meta("%s/%s")', key, value)
-        self.save_object(BibleMeta.populate(key=key, value=value))
-
     def get_book(self, book):
         """
         Return a book object from the database.

=== modified file 'openlp/plugins/bibles/lib/manager.py'
--- openlp/plugins/bibles/lib/manager.py	2011-05-29 19:32:37 +0000
+++ openlp/plugins/bibles/lib/manager.py	2011-06-10 17:19:24 +0000
@@ -34,7 +34,7 @@
 from openlp.core.lib.ui import critical_error_message_box
 from openlp.core.utils import AppLocation, delete_file
 from openlp.plugins.bibles.lib import parse_reference
-from openlp.plugins.bibles.lib.db import BibleDB, BibleMeta, OldBibleDB
+from openlp.plugins.bibles.lib.db import BibleDB, OldBibleDB
 from csvbible import CSVBible
 from http import HTTPBible
 from opensong import OpenSongBible
@@ -161,13 +161,10 @@
             log.debug(u'Bible Name: "%s"', name)
             self.db_cache[name] = bible
             # Look to see if lazy load bible exists and get create getter.
-            source = self.db_cache[name].get_object(BibleMeta,
-                u'download source')
+            source = self.get_meta_data(name, u'download source')
             if source:
-                download_name = self.db_cache[name].get_object(BibleMeta,
-                    u'download name').value
-                meta_proxy = self.db_cache[name].get_object(BibleMeta,
-                    u'proxy url')
+                download_name = self.get_meta_data(name, u'download name').value
+                meta_proxy = self.get_meta_data(name, u'proxy url')
                 web_bible = HTTPBible(self.parent, path=self.path,
                     file=filename, download_source=source.value,
                     download_name=download_name)
@@ -220,7 +217,7 @@
         return [
             {
                 u'name': book.name,
-                u'book_reference_id': book.book_reference_id, 
+                u'book_reference_id': book.book_reference_id,
                 u'chapters': self.db_cache[bible].get_chapter_count(book)
             }
             for book in self.db_cache[bible].get_books()
@@ -229,10 +226,10 @@
     def get_chapter_count(self, bible, book):
         """
         Returns the number of Chapters for a given book.
-        
+
         ``bible``
             Unicode. The Bible to get the list of books from.
-        
+
         ``book``
             The book object to get the chapter count for.
         """
@@ -295,7 +292,7 @@
                     if db_book:
                         book_id = db_book.book_reference_id
                         log.debug(u'Book name corrected to "%s"', db_book.name)
-                        new_reflist.append((book_id, item[1], item[2], 
+                        new_reflist.append((book_id, item[1], item[2],
                             item[3]))
                     else:
                         log.debug(u'OpenLP failed to find book %s', item[0])
@@ -348,11 +345,10 @@
                 })
             return None
         # Check if the bible or second_bible is a web bible.
-        webbible = self.db_cache[bible].get_object(BibleMeta,
-            u'download source')
+        webbible = self.get_meta_data(bible, u'download source')
         second_webbible = u''
         if second_bible:
-            second_webbible = self.db_cache[second_bible].get_object(BibleMeta,
+            second_webbible = self.get_meta_data(second_bible,
                 u'download source')
         if webbible or second_webbible:
             Receiver.send_message(u'openlp_information_message', {
@@ -360,7 +356,7 @@
                 'Web Bible cannot be used'),
                 u'message': translate('BiblesPlugin.BibleManager',
                 'Text Search is not available with Web Bibles.')
-                })
+            })
             return None
         if text:
             return self.db_cache[bible].verse_search(text)
@@ -391,7 +387,7 @@
         Returns the meta data for a given key.
         """
         log.debug(u'get_meta %s,%s', bible, key)
-        return self.db_cache[bible].get_object(BibleMeta, key)
+        return self.db_cache[bible].get_meta(key)
 
     def exists(self, name):
         """

=== modified file 'openlp/plugins/custom/customplugin.py'
--- openlp/plugins/custom/customplugin.py	2011-05-27 09:34:14 +0000
+++ openlp/plugins/custom/customplugin.py	2011-06-10 17:19:24 +0000
@@ -42,8 +42,17 @@
     Custom shows are designed to replace the use of songs where
     the songs plugin has become restrictive. Examples could be
     Welcome slides, Bible Reading information, Orders of service.
+
+    ``CURRENT_DB_VERSION``
+        This class constant indicates the database version number which the
+        customs database is supposed to have.
+
+        ``2``
+            Prior to 1.9.6 the customs database did not have a version number.
+            After 1.9.6 the version number was set to ``2``.
     """
     log.info(u'Custom Plugin loaded')
+    CURRENT_DB_VERSION = 2
 
     def __init__(self, plugin_helpers):
         Plugin.__init__(self, u'Custom', plugin_helpers,
@@ -53,6 +62,11 @@
         self.icon_path = u':/plugins/plugin_custom.png'
         self.icon = build_icon(self.icon_path)
 
+    def appStartup(self):
+        db_version = self.manager.get_meta(u'dbversion')
+        if db_version is None:
+            db_version = self.manager.create_meta(u'dbversion', u'2')
+
     def about(self):
         about_text = translate('CustomPlugin', '<strong>Custom Plugin</strong>'
             '<br />The custom plugin provides the ability to set up custom '

=== modified file 'openlp/plugins/presentations/presentationplugin.py'
--- openlp/plugins/presentations/presentationplugin.py	2011-06-03 06:52:16 +0000
+++ openlp/plugins/presentations/presentationplugin.py	2011-06-10 17:19:24 +0000
@@ -87,7 +87,7 @@
         to close down their applications and release resources.
         """
         log.info(u'Plugin Finalise')
-        #Ask each controller to tidy up
+        # Ask each controller to tidy up.
         for key in self.controllers:
             controller = self.controllers[key]
             if controller.enabled():

=== modified file 'openlp/plugins/songs/songsplugin.py'
--- openlp/plugins/songs/songsplugin.py	2011-05-26 17:11:22 +0000
+++ openlp/plugins/songs/songsplugin.py	2011-06-10 17:19:24 +0000
@@ -50,8 +50,17 @@
     songs. Songs are divided into verses, and the verse order can be
     specified. Authors, topics and song books can be assigned to songs
     as well.
+
+    ``CURRENT_DB_VERSION``
+        This class constant indicates the database version number which the song
+        database is supposed to have.
+
+        ``2``
+            Prior to 1.9.6 the song database did not have a version number.
+            After 1.9.6 the version number was set to ``2``.
     """
     log.info(u'Song Plugin loaded')
+    CURRENT_DB_VERSION = 2
 
     def __init__(self, plugin_helpers):
         """
@@ -63,6 +72,9 @@
         self.icon_path = u':/plugins/plugin_songs.png'
         self.icon = build_icon(self.icon_path)
 
+    def checkPreConditions(self):
+        return self.manager.is_valid_database(SongsPlugin.CURRENT_DB_VERSION)
+
     def initialise(self):
         log.info(u'Songs Initialising')
         Plugin.initialise(self)
@@ -269,3 +281,8 @@
         action_list.remove_action(self.songExportItem, UiStrings().Export)
         action_list.remove_action(self.toolsReindexItem, UiStrings().Tools)
         Plugin.finalise(self)
+
+    def appStartup(self):
+        db_version = self.manager.get_meta(u'dbversion')
+        if db_version is None:
+            db_version = self.manager.create_meta(u'dbversion', u'2')

=== modified file 'openlp/plugins/songusage/songusageplugin.py'
--- openlp/plugins/songusage/songusageplugin.py	2011-05-29 06:23:12 +0000
+++ openlp/plugins/songusage/songusageplugin.py	2011-06-10 17:19:24 +0000
@@ -42,7 +42,19 @@
 log = logging.getLogger(__name__)
 
 class SongUsagePlugin(Plugin):
+    """
+    This plugin allows to record the usage of songs.
+
+    ``CURRENT_DB_VERSION``
+        This class constant indicates the database version number which the song
+        usage database is supposed to have.
+
+        ``2``
+            Prior to 1.9.6 the song usage database did not have a version
+            number. After 1.9.6 the version number was set to ``2``.
+    """
     log.info(u'SongUsage Plugin loaded')
+    CURRENT_DB_VERSION = 2
 
     def __init__(self, plugin_helpers):
         Plugin.__init__(self, u'SongUsage', plugin_helpers)
@@ -140,9 +152,14 @@
             translate('SongUsagePlugin', 'Song Usage'))
         action_list.remove_action(self.songUsageStatus,
             translate('SongUsagePlugin', 'Song Usage'))
-        #stop any events being processed
+        # Stop any events being processed.
         self.SongUsageActive = False
 
+    def appStartup(self):
+        db_version = self.manager.get_meta(u'dbversion')
+        if db_version is None:
+            db_version = self.manager.create_meta(u'dbversion', u'2')
+
     def toggleSongUsageState(self):
         self.SongUsageActive = not self.SongUsageActive
         QtCore.QSettings().setValue(self.settingsSection + u'/active',


Follow ups