From 242abf9f697605ca0b3a8aee20e8105789b0306b Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Tue, 12 May 2015 16:30:46 -0400
Subject: [PATCH 001/105] Database backend as a plugin: this set of changes
 moves most or all of Bsddb from gramps.gen.db to gramps.plugins.database. The
 id of the plugin is 'bsddb' which can be loaded using the make_database(id,
 dbstate) API (for now).

Next step is to add an identifying text in the directory to
indicate which database backend to use.
---
 gramps/cli/arghandler.py                      |   5 +-
 gramps/cli/clidbman.py                        |   9 +-
 gramps/cli/grampscli.py                       |   6 +-
 gramps/gen/db/__init__.py                     |  27 ++-
 gramps/gen/db/backup.py                       | 209 +----------------
 gramps/gen/dbstate.py                         |   6 +-
 gramps/gen/plug/_gramplet.py                  |   1 -
 gramps/gen/plug/_manager.py                   |   5 +
 gramps/gen/plug/_pluginreg.py                 |  31 ++-
 gramps/gen/utils/callman.py                   |   3 +-
 gramps/gui/dbloader.py                        |   4 +-
 gramps/gui/dbman.py                           |  11 +-
 gramps/gui/pluginmanager.py                   |   7 +-
 gramps/plugins/database/bsddb.gpr.py          |  31 +++
 gramps/plugins/database/bsddb.py              |   3 +
 .../database/bsddb_support/__init__.py        |  96 ++++++++
 .../plugins/database/bsddb_support/backup.py  | 213 ++++++++++++++++++
 .../database/bsddb_support}/bsddbtxn.py       |   0
 .../database/bsddb_support}/cursor.py         |   0
 .../database/bsddb_support}/dictionary.py     |  24 +-
 .../database/bsddb_support}/read.py           |  45 ++--
 .../database/bsddb_support}/undoredo.py       |   8 +-
 .../database/bsddb_support}/upgrade.py        |  29 +--
 .../database/bsddb_support}/write.py          |  45 ++--
 24 files changed, 506 insertions(+), 312 deletions(-)
 create mode 100644 gramps/plugins/database/bsddb.gpr.py
 create mode 100644 gramps/plugins/database/bsddb.py
 create mode 100644 gramps/plugins/database/bsddb_support/__init__.py
 create mode 100644 gramps/plugins/database/bsddb_support/backup.py
 rename gramps/{gen/db => plugins/database/bsddb_support}/bsddbtxn.py (100%)
 rename gramps/{gen/db => plugins/database/bsddb_support}/cursor.py (100%)
 rename gramps/{gen/db => plugins/database/bsddb_support}/dictionary.py (98%)
 rename gramps/{gen/db => plugins/database/bsddb_support}/read.py (98%)
 rename gramps/{gen/db => plugins/database/bsddb_support}/undoredo.py (98%)
 rename gramps/{gen/db => plugins/database/bsddb_support}/upgrade.py (98%)
 rename gramps/{gen/db => plugins/database/bsddb_support}/write.py (99%)

diff --git a/gramps/cli/arghandler.py b/gramps/cli/arghandler.py
index 0db169d30..c7a5f5a4a 100644
--- a/gramps/cli/arghandler.py
+++ b/gramps/cli/arghandler.py
@@ -44,7 +44,7 @@ import sys
 #-------------------------------------------------------------------------
 from gramps.gen.recentfiles import recent_files
 from gramps.gen.utils.file import rm_tempdir, get_empty_tempdir
-from gramps.gen.db import DbBsddb
+from gramps.gen.db import make_database
 from .clidbman import CLIDbManager, NAME_FILE, find_locker_name
 
 from gramps.gen.plug import BasePluginManager
@@ -491,7 +491,8 @@ class ArgHandler(object):
                     self.imp_db_path, title = self.dbman.create_new_db_cli()
                 else:
                     self.imp_db_path = get_empty_tempdir("import_dbdir")
-                    newdb = DbBsddb()
+
+                    newdb = make_database("bsddb", self.dbstate)
                     newdb.write_version(self.imp_db_path)
                 
                 try:
diff --git a/gramps/cli/clidbman.py b/gramps/cli/clidbman.py
index 1a7e51d85..6ca96ec0d 100644
--- a/gramps/cli/clidbman.py
+++ b/gramps/cli/clidbman.py
@@ -54,7 +54,7 @@ _LOG = logging.getLogger(DBLOGNAME)
 #-------------------------------------------------------------------------
 from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
-from gramps.gen.db import DbBsddb
+from gramps.gen.db import make_database
 from gramps.gen.plug import BasePluginManager
 from gramps.gen.config import config
 from gramps.gen.constfunc import win, conv_to_unicode
@@ -294,7 +294,8 @@ class CLIDbManager(object):
 
         if create_db:
             # write the version number into metadata
-            newdb = DbBsddb()
+            
+            newdb = make_database("bsddb", self.dbstate)
             newdb.write_version(new_path)
 
         (tval, last) = time_val(new_path)
@@ -360,8 +361,8 @@ class CLIDbManager(object):
     
                 # Create a new database
                 self.__start_cursor(_("Importing data..."))
-                dbclass = DbBsddb
-                dbase = dbclass()
+
+                dbase = make_database("bsddb", self.dbstate)
                 dbase.load(new_path, user.callback)
     
                 import_function = plugin.get_import_function()
diff --git a/gramps/cli/grampscli.py b/gramps/cli/grampscli.py
index a45564a19..00b66d352 100644
--- a/gramps/cli/grampscli.py
+++ b/gramps/cli/grampscli.py
@@ -49,7 +49,7 @@ from gramps.gen.config import config
 from gramps.gen.const import PLUGINS_DIR, USER_PLUGINS
 from gramps.gen.errors import DbError
 from gramps.gen.dbstate import DbState
-from gramps.gen.db import DbBsddb
+from gramps.gen.db import make_database
 from gramps.gen.db.exceptions import (DbUpgradeRequiredError, 
                                       BsddbDowngradeError, 
                                       DbVersionError, 
@@ -152,9 +152,9 @@ class CLIDbLoader(object):
         else:
             mode = 'w'
 
-        dbclass = DbBsddb
+        db = make_database("bsddb", self.dbstate)
         
-        self.dbstate.change_database(dbclass())
+        self.dbstate.change_database(db)
         self.dbstate.db.disable_signals()
 
         self._begin_progress()
diff --git a/gramps/gen/db/__init__.py b/gramps/gen/db/__init__.py
index 5b79ce334..b977887b7 100644
--- a/gramps/gen/db/__init__.py
+++ b/gramps/gen/db/__init__.py
@@ -86,11 +86,26 @@ More details can be found in the manual's
 
 from .base import *
 from .dbconst import *
-from .cursor import *
-from .read import *
-from .bsddbtxn import *
+#from .cursor import *
+#from .read import *
+#from .bsddbtxn import *
 from .txn import *
-from .undoredo import *
+#from .undoredo import *
 from .exceptions import *
-from .write import *
-from .backup import backup, restore
+#from .write import *
+#from .backup import backup, restore
+
+def make_database(id, dbstate):
+    from gramps.cli.grampscli import CLIManager
+    from gramps.gen.plug import BasePluginManager
+    from gramps.cli.user import User
+
+    climanager = CLIManager(dbstate, setloader=False, user=User()) # do not load db_loader
+    climanager.do_reg_plugins(dbstate, None)
+    pmgr = BasePluginManager.get_instance()
+    pdata = pmgr.get_plugin(id)
+
+    mod = pmgr.load_plugin(pdata)
+    database = getattr(mod, pdata.databaseclass)
+    return database()
+
diff --git a/gramps/gen/db/backup.py b/gramps/gen/db/backup.py
index 9329caaff..52d4d5f05 100644
--- a/gramps/gen/db/backup.py
+++ b/gramps/gen/db/backup.py
@@ -1,213 +1,8 @@
-#
-# Gramps - a GTK+/GNOME based genealogy program
-#
-# Copyright (C) 2007  Donald N. Allingham
-# Copyright (C) 2011       Tim G L Lyons
-#
-# 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; either version 2 of the License, or
-# (at your option) any later version.
-#
-# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-# gen/db/backup.py
-
-"""
-Description
-===========
-
-This module Provides backup and restore functions for a database. The
-backup function saves the data into backup files, while the restore
-function loads the data back into a database.
-
-You should only restore the data into an empty database.
-
-Implementation
-==============
-
-Not all of the database tables need to be backed up, since many are
-automatically generated from the others. The tables that are backed up
-are the primary tables and the metadata table.
-
-The database consists of a table of "pickled" tuples. Each of the
-primary tables is "walked", and the pickled tuple is extracted, and
-written to the backup file.
-
-Restoring the data is just as simple. The backup file is parsed an
-entry at a time, and inserted into the associated database table. The
-derived tables are built automatically as the items are entered into
-db.
-"""
-
-#-------------------------------------------------------------------------
-#
-# load standard python libraries
-#
-#-------------------------------------------------------------------------
-import os
-import pickle
-
-#------------------------------------------------------------------------
-#
-# Gramps libs
-#
-#------------------------------------------------------------------------
-from .exceptions import DbException
-from .write import FAMILY_TBL, PLACES_TBL, SOURCES_TBL, MEDIA_TBL, \
-    EVENTS_TBL, PERSON_TBL, REPO_TBL, NOTE_TBL, TAG_TBL, META, CITATIONS_TBL
-
-#------------------------------------------------------------------------
-#
-# Set up logging
-#
-#------------------------------------------------------------------------
 import logging
 LOG = logging.getLogger(".Backup")
 
 def backup(database):
-    """
-    Exports the database to a set of backup files. These files consist
-    of the pickled database tables, one file for each table.
-
-    The heavy lifting is done by the private :py:func:`__do__export` function.
-    The purpose of this function is to catch any exceptions that occur.
-
-    :param database: database instance to backup
-    :type database: DbDir
-    """
-    try:
-        __do_export(database)
-    except (OSError, IOError) as msg:
-        raise DbException(str(msg))
-
-def __mk_backup_name(database, base):
-    """
-    Return the backup name of the database table
-
-    :param database: database instance 
-    :type database: DbDir
-    :param base: base name of the table
-    :type base: str
-    """
-    return os.path.join(database.get_save_path(), base + ".gbkp")
-
-def __mk_tmp_name(database, base):
-    """
-    Return the temporary backup name of the database table
-
-    :param database: database instance 
-    :type database: DbDir
-    :param base: base name of the table
-    :type base: str
-    """
-    return os.path.join(database.get_save_path(), base + ".gbkp.new")
-
-def __do_export(database):
-    """
-    Loop through each table of the database, saving the pickled data
-    a file.
-
-    :param database: database instance to backup
-    :type database: DbDir
-    """
-    try:
-        for (base, tbl) in __build_tbl_map(database):
-            backup_name = __mk_tmp_name(database, base)
-            backup_table = open(backup_name, 'wb')
-    
-            cursor = tbl.cursor()
-            data = cursor.first()
-            while data:
-                pickle.dump(data, backup_table, 2)
-                data = cursor.next()
-            cursor.close()
-            backup_table.close()
-    except (IOError,OSError):
-        return
-
-    for (base, tbl) in __build_tbl_map(database):
-        new_name = __mk_backup_name(database, base)
-        old_name = __mk_tmp_name(database, base)
-        if os.path.isfile(new_name):
-            os.unlink(new_name)
-        os.rename(old_name, new_name)
+    print("FIXME")
 
 def restore(database):
-    """
-    Restores the database to a set of backup files. These files consist
-    of the pickled database tables, one file for each table.
-
-    The heavy lifting is done by the private :py:func:`__do__restore` function.
-    The purpose of this function is to catch any exceptions that occur.
-
-    :param database: database instance to restore
-    :type database: DbDir
-    """
-    try:
-        __do_restore(database)
-    except (OSError, IOError) as msg:
-        raise DbException(str(msg))
-
-def __do_restore(database):
-    """
-    Loop through each table of the database, restoring the pickled data
-    to the appropriate database file.
-
-    :param database: database instance to backup
-    :type database: DbDir
-    """
-    for (base, tbl) in __build_tbl_map(database):
-        backup_name = __mk_backup_name(database, base)
-        backup_table = open(backup_name, 'rb')
-        __load_tbl_txn(database, backup_table, tbl)
-
-    database.rebuild_secondary()
-
-def __load_tbl_txn(database, backup_table, tbl):
-    """
-    Return the temporary backup name of the database table
-
-    :param database: database instance 
-    :type database: DbDir
-    :param backup_table: file containing the backup data
-    :type backup_table: file
-    :param tbl: Berkeley db database table
-    :type tbl: Berkeley db database table
-    """
-    try:
-        while True:
-            data = pickle.load(backup_table)
-            txn = database.env.txn_begin()
-            tbl.put(data[0], data[1], txn=txn)
-            txn.commit()
-    except EOFError:
-        backup_table.close()
-
-def __build_tbl_map(database):
-    """
-    Builds a table map of names to database tables.
-
-    :param database: database instance to backup
-    :type database: DbDir
-    """
-    return [
-        ( PERSON_TBL,  database.person_map.db),
-        ( FAMILY_TBL,  database.family_map.db),
-        ( PLACES_TBL,  database.place_map.db),
-        ( SOURCES_TBL, database.source_map.db),
-        ( CITATIONS_TBL, database.citation_map.db),
-        ( REPO_TBL,    database.repository_map.db),
-        ( NOTE_TBL,    database.note_map.db),
-        ( MEDIA_TBL,   database.media_map.db),
-        ( EVENTS_TBL,  database.event_map.db),
-        ( TAG_TBL,     database.tag_map.db),
-        ( META,        database.metadata.db),
-        ]
+    print("FIXME")
diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py
index 839d05cd2..12ee68ea9 100644
--- a/gramps/gen/dbstate.py
+++ b/gramps/gen/dbstate.py
@@ -23,8 +23,8 @@
 Provide the database state class
 """
 
-from .db import DbBsddbRead
 from .db import DbReadBase
+from .db import make_database
 from .proxy.proxybase import ProxyDbBase
 from .utils.callback import Callback
 from .config import config
@@ -45,7 +45,7 @@ class DbState(Callback):
         just a place holder until a real DB is assigned.
         """
         Callback.__init__(self)
-        self.db      = DbBsddbRead()
+        self.db      = make_database("bsddb", self)
         self.open    = False
         self.stack = []
 
@@ -88,7 +88,7 @@ class DbState(Callback):
         """
         self.emit('no-database', ())
         self.db.close()
-        self.db = DbBsddbRead()
+        self.db = make_database("bsddb", self)
         self.db.db_is_open = False
         self.open = False
         self.emit('database-changed', (self.db, ))
diff --git a/gramps/gen/plug/_gramplet.py b/gramps/gen/plug/_gramplet.py
index c040bae7d..b812a7fb7 100644
--- a/gramps/gen/plug/_gramplet.py
+++ b/gramps/gen/plug/_gramplet.py
@@ -70,7 +70,6 @@ class Gramplet(object):
         self.connect(self.gui.textview, "motion-notify-event", 
                      self.gui.on_motion)
         self.connect_signal('Person', self._active_changed)
-        
         self._db_changed(self.dbstate.db)
         active_person = self.get_active('Person')
         if active_person: # already changed
diff --git a/gramps/gen/plug/_manager.py b/gramps/gen/plug/_manager.py
index e047f6166..e30a089ae 100644
--- a/gramps/gen/plug/_manager.py
+++ b/gramps/gen/plug/_manager.py
@@ -412,6 +412,11 @@ class BasePluginManager(object):
         """
         return self.__pgr.sidebar_plugins()
     
+    def get_reg_databases(self):
+        """ Return list of registered database backends
+        """
+        return self.__pgr.database_plugins()
+    
     def get_external_opt_dict(self):
         """ Return the dictionary of external options. """
         return self.__external_opt_dict
diff --git a/gramps/gen/plug/_pluginreg.py b/gramps/gen/plug/_pluginreg.py
index 04167cafc..5619f18ed 100644
--- a/gramps/gen/plug/_pluginreg.py
+++ b/gramps/gen/plug/_pluginreg.py
@@ -70,8 +70,9 @@ VIEW        = 8
 RELCALC     = 9
 GRAMPLET    = 10
 SIDEBAR     = 11
+DATABASE    = 12
 PTYPE       = [REPORT , QUICKREPORT, TOOL, IMPORT, EXPORT, DOCGEN, GENERAL,
-               MAPSERVICE, VIEW, RELCALC, GRAMPLET, SIDEBAR]
+               MAPSERVICE, VIEW, RELCALC, GRAMPLET, SIDEBAR, DATABASE]
 PTYPE_STR   = {
         REPORT: _('Report') , 
         QUICKREPORT: _('Quickreport'), 
@@ -85,6 +86,7 @@ PTYPE_STR   = {
         RELCALC: _('Relationships'), 
         GRAMPLET: _('Gramplet'),
         SIDEBAR: _('Sidebar'),
+        DATABASE: _('Database'),
         }
 
 #possible report categories
@@ -206,7 +208,7 @@ class PluginData(object):
        The python path where the plugin implementation can be found
     .. attribute:: ptype
        The plugin type. One of REPORT , QUICKREPORT, TOOL, IMPORT,
-        EXPORT, DOCGEN, GENERAL, MAPSERVICE, VIEW, GRAMPLET
+        EXPORT, DOCGEN, GENERAL, MAPSERVICE, VIEW, GRAMPLET, DATABASE
     .. attribute:: authors
        List of authors of the plugin, default=[]
     .. attribute:: authors_email
@@ -349,6 +351,11 @@ class PluginData(object):
        the plugin is appended to the list of plugins. If START, then the
        plugin is prepended. Only set START if you want a plugin to be the
        first in the order of plugins
+
+    Attributes for DATABASE plugins
+
+    .. attribute:: databaseclass
+       The class in the module that is the database class
     """
 
     def __init__(self):
@@ -421,6 +428,8 @@ class PluginData(object):
         self._menu_label = ''
         #VIEW and SIDEBAR attr
         self._order = END
+        #DATABASE attr
+        self._databaseclass = None
         #GENERAL attr
         self._data = []
         self._process = None
@@ -931,6 +940,17 @@ class PluginData(object):
 
     order = property(_get_order, _set_order)
   
+    #DATABASE attributes
+    def _set_databaseclass(self, databaseclass):
+        if not self._ptype == DATABASE:
+            raise ValueError('databaseclass may only be set for DATABASE plugins')
+        self._databaseclass = databaseclass
+
+    def _get_databaseclass(self):
+        return self._databaseclass
+
+    databaseclass = property(_get_databaseclass, _set_databaseclass)
+
     #GENERAL attr
     def _set_data(self, data):
         if not self._ptype in (GENERAL,):
@@ -1032,6 +1052,7 @@ def make_environment(**kwargs):
         'REPORT_MODE_CLI': REPORT_MODE_CLI,
         'TOOL_MODE_GUI': TOOL_MODE_GUI, 
         'TOOL_MODE_CLI': TOOL_MODE_CLI,
+        'DATABASE': DATABASE,
         'GRAMPSVERSION': GRAMPSVERSION,
         'START': START,
         'END': END,
@@ -1297,6 +1318,12 @@ class PluginRegister(object):
         """
         return self.type_plugins(SIDEBAR)
 
+    def database_plugins(self):
+        """
+        Return a list of :class:`PluginData` that are of type DATABASE
+        """
+        return self.type_plugins(DATABASE)
+
     def filter_load_on_reg(self):
         """
         Return a list of :class:`PluginData` that have load_on_reg == True
diff --git a/gramps/gen/utils/callman.py b/gramps/gen/utils/callman.py
index ca6a94d86..939a0f6fa 100644
--- a/gramps/gen/utils/callman.py
+++ b/gramps/gen/utils/callman.py
@@ -303,7 +303,8 @@ class CallbackManager(object):
         Do a custom db connect signal outside of the primary object ones
         managed automatically.
         """
-        self.custom_signal_keys.append(self.database.connect(name, callback))
+        if self.database:
+            self.custom_signal_keys.append(self.database.connect(name, callback))
     
     def __callbackcreator(self, signal, noarg=False):
         """
diff --git a/gramps/gui/dbloader.py b/gramps/gui/dbloader.py
index 6d4b283d4..a0e180f90 100644
--- a/gramps/gui/dbloader.py
+++ b/gramps/gui/dbloader.py
@@ -55,7 +55,7 @@ from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
 from gramps.cli.grampscli import CLIDbLoader
 from gramps.gen.config import config
-from gramps.gen.db import DbBsddb
+from gramps.gen.db import make_database
 from gramps.gen.db.exceptions import (DbUpgradeRequiredError, 
                                       BsddbDowngradeError, 
                                       DbVersionError, 
@@ -305,7 +305,7 @@ class DbLoader(CLIDbLoader):
         else:
             mode = 'w'
 
-        db = DbBsddb()
+        db = make_database("bsddb", self.dbstate)
         db.disable_signals()
         self.dbstate.no_database()
 
diff --git a/gramps/gui/dbman.py b/gramps/gui/dbman.py
index f57912547..cfc2d4a68 100644
--- a/gramps/gui/dbman.py
+++ b/gramps/gui/dbman.py
@@ -73,7 +73,7 @@ _ = glocale.translation.gettext
 from gramps.gen.const import URL_WIKISTRING
 from .user import User
 from .dialog import ErrorDialog, QuestionDialog, QuestionDialog2
-from gramps.gen.db import DbBsddb
+from gramps.gen.db import make_database
 from .pluginmanager import GuiPluginManager
 from gramps.cli.clidbman import CLIDbManager, NAME_FILE, time_val
 from .ddtargets import DdTargets
@@ -535,8 +535,8 @@ class DbManager(CLIDbManager):
         new_path, newname = self._create_new_db("%s : %s" % (parent_name, name))
         
         self.__start_cursor(_("Extracting archive..."))
-        dbclass = DbBsddb
-        dbase = dbclass()
+
+        dbase = make_database("bsddb", self.dbstate)
         dbase.load(new_path, None)
         
         self.__start_cursor(_("Importing archive..."))
@@ -723,11 +723,10 @@ class DbManager(CLIDbManager):
                 fname = os.path.join(dirname, filename)
                 os.unlink(fname)
 
-        newdb = DbBsddb()
+        newdb = make_database("bsddb", self.dbstate)
         newdb.write_version(dirname)
 
-        dbclass = DbBsddb
-        dbase = dbclass()
+        dbase = make_database("bsddb", self.dbstate)
         dbase.set_save_path(dirname)
         dbase.load(dirname, None)
 
diff --git a/gramps/gui/pluginmanager.py b/gramps/gui/pluginmanager.py
index 51833b94b..9466759f8 100644
--- a/gramps/gui/pluginmanager.py
+++ b/gramps/gui/pluginmanager.py
@@ -314,7 +314,12 @@ class GuiPluginManager(Callback):
         return [plg for plg in self.basemgr.get_reg_docgens()
                                 if plg.id not in self.__hidden_plugins]
 
+    def get_reg_databases(self):
+        """ Return list of non hidden registered database backends
+        """
+        return [plg for plg in self.basemgr.get_reg_databases()
+                                if plg.id not in self.__hidden_plugins]
+
     def get_reg_general(self, category=None):
         return [plg for plg in self.basemgr.get_reg_general(category)
                                 if plg.id not in self.__hidden_plugins]
-
diff --git a/gramps/plugins/database/bsddb.gpr.py b/gramps/plugins/database/bsddb.gpr.py
new file mode 100644
index 000000000..20f2e9fda
--- /dev/null
+++ b/gramps/plugins/database/bsddb.gpr.py
@@ -0,0 +1,31 @@
+#
+# Gramps - a GTK+/GNOME based genealogy program
+#
+# Copyright (C) 2015 Douglas Blank <doug.blank@gmail.com>
+#
+# 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+plg = newplugin()
+plg.id    = 'bsddb'
+plg.name  = _("BSDDB Database Backend")
+plg.name_accell  = _("_BSDDB Database Backend")
+plg.description =  _("Berkeley Software Distribution Database Backend")
+plg.version = '1.0'
+plg.gramps_target_version = "4.2"
+plg.status = STABLE
+plg.fname = 'bsddb.py'
+plg.ptype = DATABASE
+plg.databaseclass = 'DbBsddb'
diff --git a/gramps/plugins/database/bsddb.py b/gramps/plugins/database/bsddb.py
new file mode 100644
index 000000000..9cbca25ce
--- /dev/null
+++ b/gramps/plugins/database/bsddb.py
@@ -0,0 +1,3 @@
+
+from bsddb_support import DbBsddb
+
diff --git a/gramps/plugins/database/bsddb_support/__init__.py b/gramps/plugins/database/bsddb_support/__init__.py
new file mode 100644
index 000000000..524ecb660
--- /dev/null
+++ b/gramps/plugins/database/bsddb_support/__init__.py
@@ -0,0 +1,96 @@
+#
+# Gramps - a GTK+/GNOME based genealogy program
+#
+# Copyright (C) 2000-2007  Donald N. Allingham
+#
+# 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+"""
+Gramps Database API.
+
+Database Architecture
+=====================
+
+Access to the database is made through Python classes. Exactly
+what functionality you have is dependent on the properties of the
+database. For example, if you are accessing a read-only view, then
+you will only have access to a subset of the methods available.
+
+At the root of any database interface is either :py:class:`.DbReadBase` and/or
+:py:class:`.DbWriteBase`. These define the methods to read and write to a
+database, respectively.
+
+The full database hierarchy is:
+
+- :py:class:`.DbBsddb` - read and write implementation to BSDDB databases
+
+  * :py:class:`.DbWriteBase` - virtual and implementation-independent methods
+    for reading data
+
+  * :py:class:`.DbBsddbRead` - read-only (accessors, getters) implementation
+    to BSDDB databases
+
+    + :py:class:`.DbReadBase` - virtual and implementation-independent
+      methods for reading data
+
+    + :py:class:`.Callback` - callback and signal functions
+
+  * :py:class:`.UpdateCallback` - callback functionality
+
+- :py:class:`.DbDjango` - read and write implementation to Django-based
+  databases
+
+  * :py:class:`.DbWriteBase` - virtual and implementation-independent methods
+    for reading data
+
+  * :py:class:`.DbReadBase` - virtual and implementation-independent methods
+    for reading data
+
+DbBsddb
+=======
+
+The :py:class:`.DbBsddb` interface defines a hierarchical database
+(non-relational) written in
+`PyBSDDB <http://www.jcea.es/programacion/pybsddb.htm>`_. There is no
+such thing as a database schema, and the meaning of the data is
+defined in the Python classes above. The data is stored as pickled
+tuples and unserialized into the primary data types (below).
+
+DbDjango
+========
+
+The DbDjango interface defines the Gramps data in terms of
+*models* and *relations* from the
+`Django project <http://www.djangoproject.com/>`_. The database
+backend can be any implementation that supports Django, including
+such popular SQL implementations as sqlite, MySQL, Postgresql, and
+Oracle. The data is retrieved from the SQL fields, serialized and
+then unserialized into the primary data types (below).
+
+More details can be found in the manual's
+`Using database API <http://www.gramps-project.org/wiki/index.php?title=Using_database_API>`_.
+"""
+
+from gramps.gen.db.base import *
+from gramps.gen.db.dbconst import *
+from .cursor import *
+from .read import *
+from .bsddbtxn import *
+from gramps.gen.db.txn import *
+from .undoredo import *
+from gramps.gen.db.exceptions import *
+from .write import *
+from .backup import backup, restore
diff --git a/gramps/plugins/database/bsddb_support/backup.py b/gramps/plugins/database/bsddb_support/backup.py
new file mode 100644
index 000000000..29dd46c54
--- /dev/null
+++ b/gramps/plugins/database/bsddb_support/backup.py
@@ -0,0 +1,213 @@
+#
+# Gramps - a GTK+/GNOME based genealogy program
+#
+# Copyright (C) 2007  Donald N. Allingham
+# Copyright (C) 2011       Tim G L Lyons
+#
+# 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# gen/db/backup.py
+
+"""
+Description
+===========
+
+This module Provides backup and restore functions for a database. The
+backup function saves the data into backup files, while the restore
+function loads the data back into a database.
+
+You should only restore the data into an empty database.
+
+Implementation
+==============
+
+Not all of the database tables need to be backed up, since many are
+automatically generated from the others. The tables that are backed up
+are the primary tables and the metadata table.
+
+The database consists of a table of "pickled" tuples. Each of the
+primary tables is "walked", and the pickled tuple is extracted, and
+written to the backup file.
+
+Restoring the data is just as simple. The backup file is parsed an
+entry at a time, and inserted into the associated database table. The
+derived tables are built automatically as the items are entered into
+db.
+"""
+
+#-------------------------------------------------------------------------
+#
+# load standard python libraries
+#
+#-------------------------------------------------------------------------
+import os
+import pickle
+
+#------------------------------------------------------------------------
+#
+# Gramps libs
+#
+#------------------------------------------------------------------------
+from gramps.gen.db.exceptions import DbException
+from .write import FAMILY_TBL, PLACES_TBL, SOURCES_TBL, MEDIA_TBL, \
+    EVENTS_TBL, PERSON_TBL, REPO_TBL, NOTE_TBL, TAG_TBL, META, CITATIONS_TBL
+
+#------------------------------------------------------------------------
+#
+# Set up logging
+#
+#------------------------------------------------------------------------
+import logging
+LOG = logging.getLogger(".Backup")
+
+def backup(database):
+    """
+    Exports the database to a set of backup files. These files consist
+    of the pickled database tables, one file for each table.
+
+    The heavy lifting is done by the private :py:func:`__do__export` function.
+    The purpose of this function is to catch any exceptions that occur.
+
+    :param database: database instance to backup
+    :type database: DbDir
+    """
+    try:
+        __do_export(database)
+    except (OSError, IOError) as msg:
+        raise DbException(str(msg))
+
+def __mk_backup_name(database, base):
+    """
+    Return the backup name of the database table
+
+    :param database: database instance 
+    :type database: DbDir
+    :param base: base name of the table
+    :type base: str
+    """
+    return os.path.join(database.get_save_path(), base + ".gbkp")
+
+def __mk_tmp_name(database, base):
+    """
+    Return the temporary backup name of the database table
+
+    :param database: database instance 
+    :type database: DbDir
+    :param base: base name of the table
+    :type base: str
+    """
+    return os.path.join(database.get_save_path(), base + ".gbkp.new")
+
+def __do_export(database):
+    """
+    Loop through each table of the database, saving the pickled data
+    a file.
+
+    :param database: database instance to backup
+    :type database: DbDir
+    """
+    try:
+        for (base, tbl) in __build_tbl_map(database):
+            backup_name = __mk_tmp_name(database, base)
+            backup_table = open(backup_name, 'wb')
+    
+            cursor = tbl.cursor()
+            data = cursor.first()
+            while data:
+                pickle.dump(data, backup_table, 2)
+                data = cursor.next()
+            cursor.close()
+            backup_table.close()
+    except (IOError,OSError):
+        return
+
+    for (base, tbl) in __build_tbl_map(database):
+        new_name = __mk_backup_name(database, base)
+        old_name = __mk_tmp_name(database, base)
+        if os.path.isfile(new_name):
+            os.unlink(new_name)
+        os.rename(old_name, new_name)
+
+def restore(database):
+    """
+    Restores the database to a set of backup files. These files consist
+    of the pickled database tables, one file for each table.
+
+    The heavy lifting is done by the private :py:func:`__do__restore` function.
+    The purpose of this function is to catch any exceptions that occur.
+
+    :param database: database instance to restore
+    :type database: DbDir
+    """
+    try:
+        __do_restore(database)
+    except (OSError, IOError) as msg:
+        raise DbException(str(msg))
+
+def __do_restore(database):
+    """
+    Loop through each table of the database, restoring the pickled data
+    to the appropriate database file.
+
+    :param database: database instance to backup
+    :type database: DbDir
+    """
+    for (base, tbl) in __build_tbl_map(database):
+        backup_name = __mk_backup_name(database, base)
+        backup_table = open(backup_name, 'rb')
+        __load_tbl_txn(database, backup_table, tbl)
+
+    database.rebuild_secondary()
+
+def __load_tbl_txn(database, backup_table, tbl):
+    """
+    Return the temporary backup name of the database table
+
+    :param database: database instance 
+    :type database: DbDir
+    :param backup_table: file containing the backup data
+    :type backup_table: file
+    :param tbl: Berkeley db database table
+    :type tbl: Berkeley db database table
+    """
+    try:
+        while True:
+            data = pickle.load(backup_table)
+            txn = database.env.txn_begin()
+            tbl.put(data[0], data[1], txn=txn)
+            txn.commit()
+    except EOFError:
+        backup_table.close()
+
+def __build_tbl_map(database):
+    """
+    Builds a table map of names to database tables.
+
+    :param database: database instance to backup
+    :type database: DbDir
+    """
+    return [
+        ( PERSON_TBL,  database.person_map.db),
+        ( FAMILY_TBL,  database.family_map.db),
+        ( PLACES_TBL,  database.place_map.db),
+        ( SOURCES_TBL, database.source_map.db),
+        ( CITATIONS_TBL, database.citation_map.db),
+        ( REPO_TBL,    database.repository_map.db),
+        ( NOTE_TBL,    database.note_map.db),
+        ( MEDIA_TBL,   database.media_map.db),
+        ( EVENTS_TBL,  database.event_map.db),
+        ( TAG_TBL,     database.tag_map.db),
+        ( META,        database.metadata.db),
+        ]
diff --git a/gramps/gen/db/bsddbtxn.py b/gramps/plugins/database/bsddb_support/bsddbtxn.py
similarity index 100%
rename from gramps/gen/db/bsddbtxn.py
rename to gramps/plugins/database/bsddb_support/bsddbtxn.py
diff --git a/gramps/gen/db/cursor.py b/gramps/plugins/database/bsddb_support/cursor.py
similarity index 100%
rename from gramps/gen/db/cursor.py
rename to gramps/plugins/database/bsddb_support/cursor.py
diff --git a/gramps/gen/db/dictionary.py b/gramps/plugins/database/bsddb_support/dictionary.py
similarity index 98%
rename from gramps/gen/db/dictionary.py
rename to gramps/plugins/database/bsddb_support/dictionary.py
index d3974774a..2d785ef6e 100644
--- a/gramps/gen/db/dictionary.py
+++ b/gramps/plugins/database/bsddb_support/dictionary.py
@@ -40,18 +40,18 @@ from . import (PERSON_KEY,
                NOTE_KEY,
                TAG_KEY)
 
-from ..utils.id import create_id
-from ..lib.researcher import Researcher
-from ..lib.mediaobj import MediaObject
-from ..lib.person import Person
-from ..lib.family import Family
-from ..lib.src import Source
-from ..lib.citation import Citation
-from ..lib.event import Event
-from ..lib.place import Place
-from ..lib.repo import Repository
-from ..lib.note import Note
-from ..lib.tag import Tag
+from gramps.gen.utils.id import create_id
+from gramps.gen.lib.researcher import Researcher
+from gramps.gen.lib.mediaobj import MediaObject
+from gramps.gen.lib.person import Person
+from gramps.gen.lib.family import Family
+from gramps.gen.lib.src import Source
+from gramps.gen.lib.citation import Citation
+from gramps.gen.lib.event import Event
+from gramps.gen.lib.place import Place
+from gramps.gen.lib.repo import Repository
+from gramps.gen.lib.note import Note
+from gramps.gen.lib.tag import Tag
 
 class Cursor(object):
     """
diff --git a/gramps/gen/db/read.py b/gramps/plugins/database/bsddb_support/read.py
similarity index 98%
rename from gramps/gen/db/read.py
rename to gramps/plugins/database/bsddb_support/read.py
index 87dc112d6..d51810581 100644
--- a/gramps/gen/db/read.py
+++ b/gramps/plugins/database/bsddb_support/read.py
@@ -53,30 +53,32 @@ import logging
 # GRAMPS libraries
 #
 #-------------------------------------------------------------------------
-from ..lib.mediaobj import MediaObject
-from ..lib.person import Person
-from ..lib.family import Family
-from ..lib.src import Source
-from ..lib.citation import Citation
-from ..lib.event import Event
-from ..lib.place import Place
-from ..lib.repo import Repository
-from ..lib.note import Note
-from ..lib.tag import Tag
-from ..lib.genderstats import GenderStats
-from ..lib.researcher import Researcher 
-from ..lib.nameorigintype import NameOriginType
+from gramps.gen.lib.mediaobj import MediaObject
+from gramps.gen.lib.person import Person
+from gramps.gen.lib.family import Family
+from gramps.gen.lib.src import Source
+from gramps.gen.lib.citation import Citation
+from gramps.gen.lib.event import Event
+from gramps.gen.lib.place import Place
+from gramps.gen.lib.repo import Repository
+from gramps.gen.lib.note import Note
+from gramps.gen.lib.tag import Tag
+from gramps.gen.lib.genderstats import GenderStats
+from gramps.gen.lib.researcher import Researcher 
+from gramps.gen.lib.nameorigintype import NameOriginType
 
-from .dbconst import *
-from ..utils.callback import Callback
-from ..utils.cast import conv_dbstr_to_unicode
-from . import (BsddbBaseCursor, DbReadBase)
-from ..utils.id import create_id
-from ..errors import DbError
-from ..constfunc import handle2internal, get_env_var
-from ..const import GRAMPS_LOCALE as glocale
+from gramps.gen.utils.callback import Callback
+from gramps.gen.utils.cast import conv_dbstr_to_unicode
+from . import BsddbBaseCursor
+from gramps.gen.db.base import DbReadBase
+from gramps.gen.utils.id import create_id
+from gramps.gen.errors import DbError
+from gramps.gen.constfunc import handle2internal, get_env_var
+from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
 
+from gramps.gen.db.dbconst import *
+
 LOG = logging.getLogger(DBLOGNAME)
 LOG = logging.getLogger(".citation")
 #-------------------------------------------------------------------------
@@ -84,7 +86,6 @@ LOG = logging.getLogger(".citation")
 # constants
 #
 #-------------------------------------------------------------------------
-from .dbconst import *
 
 _SIGBASE = ('person', 'family', 'source', 'citation', 
             'event',  'media', 'place', 'repository',
diff --git a/gramps/gen/db/undoredo.py b/gramps/plugins/database/bsddb_support/undoredo.py
similarity index 98%
rename from gramps/gen/db/undoredo.py
rename to gramps/plugins/database/bsddb_support/undoredo.py
index 7fed4d876..da6b3368e 100644
--- a/gramps/gen/db/undoredo.py
+++ b/gramps/plugins/database/bsddb_support/undoredo.py
@@ -43,7 +43,7 @@ except:
         DBPageNotFoundError = 0
         DBInvalidArgError = 0
         
-from ..const import GRAMPS_LOCALE as glocale
+from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
 
 #-------------------------------------------------------------------------
@@ -51,10 +51,10 @@ _ = glocale.translation.gettext
 # Gramps modules
 #
 #-------------------------------------------------------------------------
-from ..constfunc import conv_to_unicode, handle2internal, win
-from .dbconst import *
+from gramps.gen.constfunc import conv_to_unicode, handle2internal, win
+from gramps.gen.db.dbconst import *
 from . import BSDDBTxn
-from ..errors import DbError
+from gramps.gen.errors import DbError
 
 #-------------------------------------------------------------------------
 #
diff --git a/gramps/gen/db/upgrade.py b/gramps/plugins/database/bsddb_support/upgrade.py
similarity index 98%
rename from gramps/gen/db/upgrade.py
rename to gramps/plugins/database/bsddb_support/upgrade.py
index 29c5eb8ab..100014e65 100644
--- a/gramps/gen/db/upgrade.py
+++ b/gramps/plugins/database/bsddb_support/upgrade.py
@@ -39,22 +39,23 @@ from bsddb3 import db
 # Gramps modules
 #
 #-------------------------------------------------------------------------
-from ..const import GRAMPS_LOCALE as glocale
+from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
-from ..constfunc import handle2internal
-from ..lib.markertype import MarkerType
-from ..lib.nameorigintype import NameOriginType
-from ..lib.place import Place
-from ..lib.placeref import PlaceRef
-from ..lib.placetype import PlaceType
-from ..lib.eventtype import EventType
-from ..lib.tag import Tag
-from ..utils.file import create_checksum
-from ..utils.id import create_id
+from gramps.gen.constfunc import handle2internal
+from gramps.gen.lib.markertype import MarkerType
+from gramps.gen.lib.nameorigintype import NameOriginType
+from gramps.gen.lib.place import Place
+from gramps.gen.lib.placeref import PlaceRef
+from gramps.gen.lib.placetype import PlaceType
+from gramps.gen.lib.eventtype import EventType
+from gramps.gen.lib.tag import Tag
+from gramps.gen.utils.file import create_checksum
+from gramps.gen.utils.id import create_id
 from . import BSDDBTxn
 from .write import _mkname, SURNAMES
-from .dbconst import (PERSON_KEY, FAMILY_KEY, EVENT_KEY, 
-                      MEDIA_KEY, PLACE_KEY, REPOSITORY_KEY, SOURCE_KEY)
+from gramps.gen.db.dbconst import (PERSON_KEY, FAMILY_KEY, EVENT_KEY, 
+                                   MEDIA_KEY, PLACE_KEY, REPOSITORY_KEY, 
+                                   SOURCE_KEY)
 from gramps.gui.dialog import (InfoDialog)
 
 LOG = logging.getLogger(".upgrade")
@@ -324,7 +325,7 @@ def upgrade_datamap_17(datamap):
     """
     new_srcattr_list = []
     private = False
-    from ..lib.srcattrtype import SrcAttributeType
+    from gramps.gen.lib.srcattrtype import SrcAttributeType
     for (key, value) in datamap.items():
         the_type = SrcAttributeType(key).serialize()
         new_srcattr_list.append((private, the_type, value))
diff --git a/gramps/gen/db/write.py b/gramps/plugins/database/bsddb_support/write.py
similarity index 99%
rename from gramps/gen/db/write.py
rename to gramps/plugins/database/bsddb_support/write.py
index bcf4ece07..55dcf771d 100644
--- a/gramps/gen/db/write.py
+++ b/gramps/plugins/database/bsddb_support/write.py
@@ -56,33 +56,34 @@ except:
 # Gramps modules
 #
 #-------------------------------------------------------------------------
-from ..lib.person import Person
-from ..lib.family import Family
-from ..lib.src import Source
-from ..lib.citation import Citation
-from ..lib.event import Event
-from ..lib.place import Place
-from ..lib.repo import Repository
-from ..lib.mediaobj import MediaObject
-from ..lib.note import Note
-from ..lib.tag import Tag
-from ..lib.genderstats import GenderStats
-from ..lib.researcher import Researcher 
+from gramps.gen.lib.person import Person
+from gramps.gen.lib.family import Family
+from gramps.gen.lib.src import Source
+from gramps.gen.lib.citation import Citation
+from gramps.gen.lib.event import Event
+from gramps.gen.lib.place import Place
+from gramps.gen.lib.repo import Repository
+from gramps.gen.lib.mediaobj import MediaObject
+from gramps.gen.lib.note import Note
+from gramps.gen.lib.tag import Tag
+from gramps.gen.lib.genderstats import GenderStats
+from gramps.gen.lib.researcher import Researcher 
 
 from . import (DbBsddbRead, DbWriteBase, BSDDBTxn, 
                     DbTxn, BsddbBaseCursor, BsddbDowngradeError, DbVersionError,
                     DbEnvironmentError, DbUpgradeRequiredError, find_surname,
-                    find_byte_surname, find_surname_name, DbUndoBSDDB as DbUndo,
-                    exceptions)
-from .dbconst import *
-from ..utils.callback import Callback
-from ..utils.cast import conv_dbstr_to_unicode
-from ..utils.id import create_id
-from ..updatecallback import UpdateCallback
-from ..errors import DbError
-from ..constfunc import (win, conv_to_unicode, handle2internal,
+                    find_byte_surname, find_surname_name, DbUndoBSDDB as DbUndo)
+
+from gramps.gen.db import exceptions
+from gramps.gen.db.dbconst import *
+from gramps.gen.utils.callback import Callback
+from gramps.gen.utils.cast import conv_dbstr_to_unicode
+from gramps.gen.utils.id import create_id
+from gramps.gen.updatecallback import UpdateCallback
+from gramps.gen.errors import DbError
+from gramps.gen.constfunc import (win, conv_to_unicode, handle2internal,
                          get_env_var)
-from ..const import HOME_DIR, GRAMPS_LOCALE as glocale
+from gramps.gen.const import HOME_DIR, GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
 
 _LOG = logging.getLogger(DBLOGNAME)

From 2dd365f8bcc6ed738ea8e07feb738b488923f429 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Tue, 12 May 2015 19:09:17 -0400
Subject: [PATCH 002/105] Moved make_database to DbState

---
 gramps/cli/arghandler.py  |  3 +--
 gramps/cli/clidbman.py    |  5 ++---
 gramps/cli/grampscli.py   |  3 +--
 gramps/gen/db/__init__.py | 15 ---------------
 gramps/gen/dbstate.py     | 26 +++++++++++++++++++++++---
 gramps/gui/dbloader.py    |  3 +--
 gramps/gui/dbman.py       |  7 +++----
 7 files changed, 31 insertions(+), 31 deletions(-)

diff --git a/gramps/cli/arghandler.py b/gramps/cli/arghandler.py
index c7a5f5a4a..47e9b8da6 100644
--- a/gramps/cli/arghandler.py
+++ b/gramps/cli/arghandler.py
@@ -44,7 +44,6 @@ import sys
 #-------------------------------------------------------------------------
 from gramps.gen.recentfiles import recent_files
 from gramps.gen.utils.file import rm_tempdir, get_empty_tempdir
-from gramps.gen.db import make_database
 from .clidbman import CLIDbManager, NAME_FILE, find_locker_name
 
 from gramps.gen.plug import BasePluginManager
@@ -492,7 +491,7 @@ class ArgHandler(object):
                 else:
                     self.imp_db_path = get_empty_tempdir("import_dbdir")
 
-                    newdb = make_database("bsddb", self.dbstate)
+                    newdb = self.dbstate.make_database("bsddb")
                     newdb.write_version(self.imp_db_path)
                 
                 try:
diff --git a/gramps/cli/clidbman.py b/gramps/cli/clidbman.py
index 6ca96ec0d..c83034e7a 100644
--- a/gramps/cli/clidbman.py
+++ b/gramps/cli/clidbman.py
@@ -54,7 +54,6 @@ _LOG = logging.getLogger(DBLOGNAME)
 #-------------------------------------------------------------------------
 from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
-from gramps.gen.db import make_database
 from gramps.gen.plug import BasePluginManager
 from gramps.gen.config import config
 from gramps.gen.constfunc import win, conv_to_unicode
@@ -295,7 +294,7 @@ class CLIDbManager(object):
         if create_db:
             # write the version number into metadata
             
-            newdb = make_database("bsddb", self.dbstate)
+            newdb = self.dbstate.make_database("bsddb")
             newdb.write_version(new_path)
 
         (tval, last) = time_val(new_path)
@@ -362,7 +361,7 @@ class CLIDbManager(object):
                 # Create a new database
                 self.__start_cursor(_("Importing data..."))
 
-                dbase = make_database("bsddb", self.dbstate)
+                dbase = self.dbstate.make_database("bsddb")
                 dbase.load(new_path, user.callback)
     
                 import_function = plugin.get_import_function()
diff --git a/gramps/cli/grampscli.py b/gramps/cli/grampscli.py
index 00b66d352..d1d3cfc07 100644
--- a/gramps/cli/grampscli.py
+++ b/gramps/cli/grampscli.py
@@ -49,7 +49,6 @@ from gramps.gen.config import config
 from gramps.gen.const import PLUGINS_DIR, USER_PLUGINS
 from gramps.gen.errors import DbError
 from gramps.gen.dbstate import DbState
-from gramps.gen.db import make_database
 from gramps.gen.db.exceptions import (DbUpgradeRequiredError, 
                                       BsddbDowngradeError, 
                                       DbVersionError, 
@@ -152,7 +151,7 @@ class CLIDbLoader(object):
         else:
             mode = 'w'
 
-        db = make_database("bsddb", self.dbstate)
+        db = self.dbstate.make_database("bsddb")
         
         self.dbstate.change_database(db)
         self.dbstate.db.disable_signals()
diff --git a/gramps/gen/db/__init__.py b/gramps/gen/db/__init__.py
index b977887b7..4ddaf5386 100644
--- a/gramps/gen/db/__init__.py
+++ b/gramps/gen/db/__init__.py
@@ -94,18 +94,3 @@ from .txn import *
 from .exceptions import *
 #from .write import *
 #from .backup import backup, restore
-
-def make_database(id, dbstate):
-    from gramps.cli.grampscli import CLIManager
-    from gramps.gen.plug import BasePluginManager
-    from gramps.cli.user import User
-
-    climanager = CLIManager(dbstate, setloader=False, user=User()) # do not load db_loader
-    climanager.do_reg_plugins(dbstate, None)
-    pmgr = BasePluginManager.get_instance()
-    pdata = pmgr.get_plugin(id)
-
-    mod = pmgr.load_plugin(pdata)
-    database = getattr(mod, pdata.databaseclass)
-    return database()
-
diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py
index 12ee68ea9..04a8e683d 100644
--- a/gramps/gen/dbstate.py
+++ b/gramps/gen/dbstate.py
@@ -24,7 +24,6 @@ Provide the database state class
 """
 
 from .db import DbReadBase
-from .db import make_database
 from .proxy.proxybase import ProxyDbBase
 from .utils.callback import Callback
 from .config import config
@@ -45,7 +44,7 @@ class DbState(Callback):
         just a place holder until a real DB is assigned.
         """
         Callback.__init__(self)
-        self.db      = make_database("bsddb", self)
+        self.db      = self.make_database("bsddb")
         self.open    = False
         self.stack = []
 
@@ -88,7 +87,7 @@ class DbState(Callback):
         """
         self.emit('no-database', ())
         self.db.close()
-        self.db = make_database("bsddb", self)
+        self.db = self.make_database("bsddb")
         self.db.db_is_open = False
         self.open = False
         self.emit('database-changed', (self.db, ))
@@ -122,3 +121,24 @@ class DbState(Callback):
         """
         self.db = self.stack.pop()
         self.emit('database-changed', (self.db, ))
+
+    def make_database(self, id):
+        """
+        Make a database, given a plugin id.
+        """
+        from .plug import BasePluginManager
+        from .const import PLUGINS_DIR, USER_PLUGINS
+
+        pmgr = BasePluginManager.get_instance()
+        pdata = pmgr.get_plugin(id)
+        
+        if not pdata:
+            # This might happen if using gramps from outside, and
+            # we haven't loaded plugins yet
+            pmgr.reg_plugins(PLUGINS_DIR, self, None)
+            pmgr.reg_plugins(USER_PLUGINS, self, None, load_on_reg=True)
+            pdata = pmgr.get_plugin(id)
+
+        mod = pmgr.load_plugin(pdata)
+        database = getattr(mod, pdata.databaseclass)
+        return database()
diff --git a/gramps/gui/dbloader.py b/gramps/gui/dbloader.py
index a0e180f90..a0105d9a3 100644
--- a/gramps/gui/dbloader.py
+++ b/gramps/gui/dbloader.py
@@ -55,7 +55,6 @@ from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
 from gramps.cli.grampscli import CLIDbLoader
 from gramps.gen.config import config
-from gramps.gen.db import make_database
 from gramps.gen.db.exceptions import (DbUpgradeRequiredError, 
                                       BsddbDowngradeError, 
                                       DbVersionError, 
@@ -305,7 +304,7 @@ class DbLoader(CLIDbLoader):
         else:
             mode = 'w'
 
-        db = make_database("bsddb", self.dbstate)
+        db = self.dbstate.make_database("bsddb")
         db.disable_signals()
         self.dbstate.no_database()
 
diff --git a/gramps/gui/dbman.py b/gramps/gui/dbman.py
index cfc2d4a68..a9ece2272 100644
--- a/gramps/gui/dbman.py
+++ b/gramps/gui/dbman.py
@@ -73,7 +73,6 @@ _ = glocale.translation.gettext
 from gramps.gen.const import URL_WIKISTRING
 from .user import User
 from .dialog import ErrorDialog, QuestionDialog, QuestionDialog2
-from gramps.gen.db import make_database
 from .pluginmanager import GuiPluginManager
 from gramps.cli.clidbman import CLIDbManager, NAME_FILE, time_val
 from .ddtargets import DdTargets
@@ -536,7 +535,7 @@ class DbManager(CLIDbManager):
         
         self.__start_cursor(_("Extracting archive..."))
 
-        dbase = make_database("bsddb", self.dbstate)
+        dbase = self.dbstate.make_database("bsddb")
         dbase.load(new_path, None)
         
         self.__start_cursor(_("Importing archive..."))
@@ -723,10 +722,10 @@ class DbManager(CLIDbManager):
                 fname = os.path.join(dirname, filename)
                 os.unlink(fname)
 
-        newdb = make_database("bsddb", self.dbstate)
+        newdb = self.dbstate.make_database("bsddb")
         newdb.write_version(dirname)
 
-        dbase = make_database("bsddb", self.dbstate)
+        dbase = self.dbstate.make_database("bsddb")
         dbase.set_save_path(dirname)
         dbase.load(dirname, None)
 

From af0b308b1e92644aea7730dde53c62af4236a1b3 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Tue, 12 May 2015 22:03:10 -0400
Subject: [PATCH 003/105] Only BSDDB plugin needs bsddb3; back/restore moved to
 db

---
 gramps/cli/clidbman.py                        |  53 +-----
 gramps/gen/db/__init__.py                     |   6 -
 gramps/gen/db/backup.py                       |   8 -
 gramps/gen/db/dbconst.py                      |  15 +-
 gramps/gen/plug/_gramplet.py                  |   6 -
 gramps/grampsapp.py                           |   8 -
 gramps/gui/aboutdialog.py                     |   9 +-
 gramps/gui/dbman.py                           |   3 +-
 gramps/gui/editors/editfamily.py              |  10 +-
 gramps/gui/logger/_errorreportassistant.py    |   9 +-
 gramps/gui/viewmanager.py                     |   3 +-
 .../database/bsddb_support/__init__.py        |   1 -
 .../plugins/database/bsddb_support/backup.py  | 139 ----------------
 .../plugins/database/bsddb_support/summary.py |  62 +++++++
 .../database/bsddb_support}/test/__init__.py  |   0
 .../bsddb_support}/test/cursor_test.py        |   0
 .../database/bsddb_support}/test/db_test.py   |   0
 .../bsddb_support}/test/grampsdbtestbase.py   |   0
 .../bsddb_support}/test/reference_map_test.py |   0
 .../plugins/database/bsddb_support/write.py   | 156 ++++++++++++++++--
 .../{bsddb_support => }/dictionary.py         |   0
 gramps/plugins/gramplet/leak.py               |   8 +-
 22 files changed, 239 insertions(+), 257 deletions(-)
 delete mode 100644 gramps/gen/db/backup.py
 create mode 100644 gramps/plugins/database/bsddb_support/summary.py
 rename gramps/{gen/db => plugins/database/bsddb_support}/test/__init__.py (100%)
 rename gramps/{gen/db => plugins/database/bsddb_support}/test/cursor_test.py (100%)
 rename gramps/{gen/db => plugins/database/bsddb_support}/test/db_test.py (100%)
 rename gramps/{gen/db => plugins/database/bsddb_support}/test/grampsdbtestbase.py (100%)
 rename gramps/{gen/db => plugins/database/bsddb_support}/test/reference_map_test.py (100%)
 rename gramps/plugins/database/{bsddb_support => }/dictionary.py (100%)

diff --git a/gramps/cli/clidbman.py b/gramps/cli/clidbman.py
index c83034e7a..7c77876f1 100644
--- a/gramps/cli/clidbman.py
+++ b/gramps/cli/clidbman.py
@@ -137,57 +137,8 @@ class CLIDbManager(object):
         current DB.
         Returns ("Unknown", "Unknown", "Unknown") if invalid DB or other error.
         """
-        from bsddb3 import dbshelve, db
-
-        from gramps.gen.db import META, PERSON_TBL
-        from  gramps.gen.db.dbconst import BDBVERSFN
-
-        bdbversion_file = os.path.join(dirpath, BDBVERSFN)
-        if os.path.isfile(bdbversion_file):
-            vers_file = open(bdbversion_file)
-            bsddb_version = vers_file.readline().strip()
-        else:
-            return "Unknown", "Unknown", "Unknown"
-        
-        current_bsddb_version = str(db.version())
-        if bsddb_version != current_bsddb_version:
-            return "Unknown", bsddb_version, "Unknown"
-        
-        env = db.DBEnv()
-        flags = db.DB_CREATE | db.DB_PRIVATE |\
-            db.DB_INIT_MPOOL |\
-            db.DB_INIT_LOG | db.DB_INIT_TXN
-        try:
-            env.open(dirpath, flags)
-        except Exception as msg:
-            LOG.warning("Error opening db environment for '%s': %s" %
-                        (name, str(msg)))
-            try:
-                env.close()
-            except Exception as msg:
-                LOG.warning("Error closing db environment for '%s': %s" %
-                        (name, str(msg)))
-            return "Unknown", bsddb_version, "Unknown"
-        dbmap1 = dbshelve.DBShelf(env)
-        fname = os.path.join(dirpath, META + ".db")
-        try:
-            dbmap1.open(fname, META, db.DB_HASH, db.DB_RDONLY)
-        except:
-            env.close()
-            return "Unknown", bsddb_version, "Unknown"
-        schema_version = dbmap1.get(b'version', default=None)
-        dbmap1.close()
-        dbmap2 = dbshelve.DBShelf(env)
-        fname = os.path.join(dirpath, PERSON_TBL + ".db")
-        try:
-            dbmap2.open(fname, PERSON_TBL, db.DB_HASH, db.DB_RDONLY)
-        except:
-            env.close()
-            return "Unknown", bsddb_version, schema_version
-        count = len(dbmap2)
-        dbmap2.close()
-        env.close()
-        return (count, bsddb_version, schema_version)
+        ## Maybe return the txt file contents, for now
+        return ("Unknown", "Unknown", "Unknown")
 
     def family_tree_summary(self):
         """
diff --git a/gramps/gen/db/__init__.py b/gramps/gen/db/__init__.py
index 4ddaf5386..066571e38 100644
--- a/gramps/gen/db/__init__.py
+++ b/gramps/gen/db/__init__.py
@@ -86,11 +86,5 @@ More details can be found in the manual's
 
 from .base import *
 from .dbconst import *
-#from .cursor import *
-#from .read import *
-#from .bsddbtxn import *
 from .txn import *
-#from .undoredo import *
 from .exceptions import *
-#from .write import *
-#from .backup import backup, restore
diff --git a/gramps/gen/db/backup.py b/gramps/gen/db/backup.py
deleted file mode 100644
index 52d4d5f05..000000000
--- a/gramps/gen/db/backup.py
+++ /dev/null
@@ -1,8 +0,0 @@
-import logging
-LOG = logging.getLogger(".Backup")
-
-def backup(database):
-    print("FIXME")
-
-def restore(database):
-    print("FIXME")
diff --git a/gramps/gen/db/dbconst.py b/gramps/gen/db/dbconst.py
index ba3514afb..7b91ab7e6 100644
--- a/gramps/gen/db/dbconst.py
+++ b/gramps/gen/db/dbconst.py
@@ -31,8 +31,7 @@ Declare constants used by database modules
 __all__ = (
             ('DBPAGE', 'DBMODE', 'DBCACHE', 'DBLOCKS', 'DBOBJECTS', 'DBUNDO',
              'DBEXT', 'DBMODE_R', 'DBMODE_W', 'DBUNDOFN', 'DBLOCKFN',
-             'DBRECOVFN','BDBVERSFN', 'DBLOGNAME', 'DBFLAGS_O',  'DBFLAGS_R',
-             'DBFLAGS_D', 'SCHVERSFN', 'PCKVERSFN'
+             'DBRECOVFN','BDBVERSFN', 'DBLOGNAME', 'SCHVERSFN', 'PCKVERSFN'
             ) +
             
             ('PERSON_KEY', 'FAMILY_KEY', 'SOURCE_KEY', 'CITATION_KEY',
@@ -60,18 +59,6 @@ DBLOCKS   = 100000          # Maximum number of locks supported
 DBOBJECTS = 100000          # Maximum number of simultaneously locked objects
 DBUNDO    = 1000            # Maximum size of undo buffer
 
-try:
-    from bsddb3.db import DB_CREATE, DB_AUTO_COMMIT, DB_DUP, DB_DUPSORT, DB_RDONLY
-    DBFLAGS_O = DB_CREATE | DB_AUTO_COMMIT  # Default flags for database open
-    DBFLAGS_R = DB_RDONLY                   # Flags to open a database read-only
-    DBFLAGS_D = DB_DUP | DB_DUPSORT         # Default flags for duplicate keys
-except:
-    print("WARNING: no bsddb support")
-    # FIXME: make this more abstract to deal with other backends, or do not import
-    DBFLAGS_O = DB_CREATE = DB_AUTO_COMMIT = 0
-    DBFLAGS_R = DB_RDONLY = 0
-    DBFLAGS_D = DB_DUP = DB_DUPSORT = 0
-
 PERSON_KEY     = 0
 FAMILY_KEY     = 1
 SOURCE_KEY     = 2
diff --git a/gramps/gen/plug/_gramplet.py b/gramps/gen/plug/_gramplet.py
index b812a7fb7..0159a8d03 100644
--- a/gramps/gen/plug/_gramplet.py
+++ b/gramps/gen/plug/_gramplet.py
@@ -320,8 +320,6 @@ class Gramplet(object):
             self._idle_id = 0
             LOG.debug("gramplet updater: %s : One time, done!" % self.gui.title)
             return False
-        # FIXME: find out why Data Entry has this error, or just ignore it
-        import bsddb3 as bsddb
         try:
             retval = next(self._generator)
             if not retval:
@@ -332,10 +330,6 @@ class Gramplet(object):
             LOG.debug("gramplet updater: %s: return %s" % 
                       (self.gui.title, retval))
             return retval
-        except bsddb.db.DBCursorClosedError:
-            # not sure why---caused by Data Entry Gramplet
-            LOG.warn("bsddb.db.DBCursorClosedError in: %s" % self.gui.title)
-            return False
         except StopIteration:
             self._idle_id = 0
             self._generator.close()
diff --git a/gramps/grampsapp.py b/gramps/grampsapp.py
index b9e4a6015..e2d7215e7 100644
--- a/gramps/grampsapp.py
+++ b/gramps/grampsapp.py
@@ -136,14 +136,6 @@ if not sys.version_info >= MIN_PYTHON_VERSION :
              'v3': MIN_PYTHON_VERSION[2]})
     sys.exit(1)
 
-try:
-    import bsddb3
-except ImportError:
-    logging.warning(_("\nYou don't have the python3 bsddb3 package installed."
-        " This package is needed to start Gramps.\n\n"
-         "Gramps will terminate now."))
-    sys.exit(1)
-    
 #-------------------------------------------------------------------------
 #
 # Gramps libraries
diff --git a/gramps/gui/aboutdialog.py b/gramps/gui/aboutdialog.py
index 264983cc1..7ab181cc2 100644
--- a/gramps/gui/aboutdialog.py
+++ b/gramps/gui/aboutdialog.py
@@ -28,7 +28,12 @@
 import os
 import sys
 import io
-import bsddb3 as bsddb
+
+try:
+    import bsddb3 as bsddb ## ok, in try/except
+    BSDDB_STR = ellipses(str(bsddb.__version__) + " " + str(bsddb.db.version()))
+except:
+    BSDDB_STR = 'not found'
 
 ##import logging
 ##_LOG = logging.getLogger(".GrampsAboutDialog")
@@ -125,7 +130,7 @@ class GrampsAboutDialog(Gtk.AboutDialog):
                  "Distribution: %s")
                 % (ellipses(str(VERSION)),
                    ellipses(str(sys.version).replace('\n','')),
-                   ellipses(str(bsddb.__version__) + " " + str(bsddb.db.version())),
+                   BSDDB_STR,
                    ellipses(get_env_var('LANG','')),
                    ellipses(operatingsystem),
                    ellipses(distribution)))
diff --git a/gramps/gui/dbman.py b/gramps/gui/dbman.py
index a9ece2272..60f28bb6f 100644
--- a/gramps/gui/dbman.py
+++ b/gramps/gui/dbman.py
@@ -78,7 +78,6 @@ from gramps.cli.clidbman import CLIDbManager, NAME_FILE, time_val
 from .ddtargets import DdTargets
 from gramps.gen.recentfiles import rename_filename, remove_filename
 from .glade import Glade
-from gramps.gen.db.backup import restore
 from gramps.gen.db.exceptions import DbException
 
 
@@ -732,7 +731,7 @@ class DbManager(CLIDbManager):
         self.__start_cursor(_("Rebuilding database from backup files"))
         
         try:
-            restore(dbase)
+            dbase.restore()
         except DbException as msg:
             DbManager.ERROR(_("Error restoring backup data"), msg)
 
diff --git a/gramps/gui/editors/editfamily.py b/gramps/gui/editors/editfamily.py
index 61071debc..2a3ee69cf 100644
--- a/gramps/gui/editors/editfamily.py
+++ b/gramps/gui/editors/editfamily.py
@@ -26,7 +26,6 @@
 # python modules
 #
 #-------------------------------------------------------------------------
-from bsddb3 import db as bsddb_db
 import pickle
 
 #-------------------------------------------------------------------------
@@ -1027,10 +1026,11 @@ class EditFamily(EditPrimary):
                )
             
     def save(self, *obj):
-        try:
-            self.__do_save()
-        except bsddb_db.DBRunRecoveryError as msg:
-            RunDatabaseRepair(msg[1])
+        ## FIXME: how to catch a specific error?
+        #try:
+        self.__do_save()
+        #except bsddb_db.DBRunRecoveryError as msg:
+        #    RunDatabaseRepair(msg[1])
 
     def __do_save(self):
         self.ok_button.set_sensitive(False)
diff --git a/gramps/gui/logger/_errorreportassistant.py b/gramps/gui/logger/_errorreportassistant.py
index d032e306f..d1972dbd6 100644
--- a/gramps/gui/logger/_errorreportassistant.py
+++ b/gramps/gui/logger/_errorreportassistant.py
@@ -30,7 +30,12 @@ from gi.repository import GdkPixbuf
 from gi.repository import GObject
 import cairo
 import sys, os
-import bsddb3 as bsddb
+
+try:
+    import bsddb3 as bsddb # ok, in try/except
+    BSDDB_STR = str(bsddb.__version__) + " " + str(bsddb.db.version())
+except:
+    BSDDB_STR = 'not found'
     
 #-------------------------------------------------------------------------
 #
@@ -166,7 +171,7 @@ class ErrorReportAssistant(Gtk.Assistant):
                "gobject version: %s\n"\
                "cairo version  : %s"\
                % (str(sys.version).replace('\n',''),
-                  str(bsddb.__version__) + " " + str(bsddb.db.version()),
+                  BSDDB_STR,
                   str(VERSION),
                   get_env_var('LANG',''),
                   operatingsystem,
diff --git a/gramps/gui/viewmanager.py b/gramps/gui/viewmanager.py
index 75b4271fc..a296f0122 100644
--- a/gramps/gui/viewmanager.py
+++ b/gramps/gui/viewmanager.py
@@ -87,7 +87,6 @@ from gramps.gen.utils.file import media_path_full
 from .dbloader import DbLoader
 from .display import display_help, display_url
 from .configure import GrampsPreferences
-from gramps.gen.db.backup import backup
 from gramps.gen.db.exceptions import DbException
 from .aboutdialog import GrampsAboutDialog
 from .navigator import Navigator
@@ -760,7 +759,7 @@ class ViewManager(CLIManager):
             self.uistate.progress.show()
             self.uistate.push_message(self.dbstate, _("Autobackup..."))
             try:
-                backup(self.dbstate.db)
+                self.dbstate.db.backup()
             except DbException as msg:
                 ErrorDialog(_("Error saving backup data"), msg)
             self.uistate.set_busy_cursor(False)
diff --git a/gramps/plugins/database/bsddb_support/__init__.py b/gramps/plugins/database/bsddb_support/__init__.py
index 524ecb660..1dd4bce56 100644
--- a/gramps/plugins/database/bsddb_support/__init__.py
+++ b/gramps/plugins/database/bsddb_support/__init__.py
@@ -93,4 +93,3 @@ from gramps.gen.db.txn import *
 from .undoredo import *
 from gramps.gen.db.exceptions import *
 from .write import *
-from .backup import backup, restore
diff --git a/gramps/plugins/database/bsddb_support/backup.py b/gramps/plugins/database/bsddb_support/backup.py
index 29dd46c54..94350da56 100644
--- a/gramps/plugins/database/bsddb_support/backup.py
+++ b/gramps/plugins/database/bsddb_support/backup.py
@@ -72,142 +72,3 @@ from .write import FAMILY_TBL, PLACES_TBL, SOURCES_TBL, MEDIA_TBL, \
 import logging
 LOG = logging.getLogger(".Backup")
 
-def backup(database):
-    """
-    Exports the database to a set of backup files. These files consist
-    of the pickled database tables, one file for each table.
-
-    The heavy lifting is done by the private :py:func:`__do__export` function.
-    The purpose of this function is to catch any exceptions that occur.
-
-    :param database: database instance to backup
-    :type database: DbDir
-    """
-    try:
-        __do_export(database)
-    except (OSError, IOError) as msg:
-        raise DbException(str(msg))
-
-def __mk_backup_name(database, base):
-    """
-    Return the backup name of the database table
-
-    :param database: database instance 
-    :type database: DbDir
-    :param base: base name of the table
-    :type base: str
-    """
-    return os.path.join(database.get_save_path(), base + ".gbkp")
-
-def __mk_tmp_name(database, base):
-    """
-    Return the temporary backup name of the database table
-
-    :param database: database instance 
-    :type database: DbDir
-    :param base: base name of the table
-    :type base: str
-    """
-    return os.path.join(database.get_save_path(), base + ".gbkp.new")
-
-def __do_export(database):
-    """
-    Loop through each table of the database, saving the pickled data
-    a file.
-
-    :param database: database instance to backup
-    :type database: DbDir
-    """
-    try:
-        for (base, tbl) in __build_tbl_map(database):
-            backup_name = __mk_tmp_name(database, base)
-            backup_table = open(backup_name, 'wb')
-    
-            cursor = tbl.cursor()
-            data = cursor.first()
-            while data:
-                pickle.dump(data, backup_table, 2)
-                data = cursor.next()
-            cursor.close()
-            backup_table.close()
-    except (IOError,OSError):
-        return
-
-    for (base, tbl) in __build_tbl_map(database):
-        new_name = __mk_backup_name(database, base)
-        old_name = __mk_tmp_name(database, base)
-        if os.path.isfile(new_name):
-            os.unlink(new_name)
-        os.rename(old_name, new_name)
-
-def restore(database):
-    """
-    Restores the database to a set of backup files. These files consist
-    of the pickled database tables, one file for each table.
-
-    The heavy lifting is done by the private :py:func:`__do__restore` function.
-    The purpose of this function is to catch any exceptions that occur.
-
-    :param database: database instance to restore
-    :type database: DbDir
-    """
-    try:
-        __do_restore(database)
-    except (OSError, IOError) as msg:
-        raise DbException(str(msg))
-
-def __do_restore(database):
-    """
-    Loop through each table of the database, restoring the pickled data
-    to the appropriate database file.
-
-    :param database: database instance to backup
-    :type database: DbDir
-    """
-    for (base, tbl) in __build_tbl_map(database):
-        backup_name = __mk_backup_name(database, base)
-        backup_table = open(backup_name, 'rb')
-        __load_tbl_txn(database, backup_table, tbl)
-
-    database.rebuild_secondary()
-
-def __load_tbl_txn(database, backup_table, tbl):
-    """
-    Return the temporary backup name of the database table
-
-    :param database: database instance 
-    :type database: DbDir
-    :param backup_table: file containing the backup data
-    :type backup_table: file
-    :param tbl: Berkeley db database table
-    :type tbl: Berkeley db database table
-    """
-    try:
-        while True:
-            data = pickle.load(backup_table)
-            txn = database.env.txn_begin()
-            tbl.put(data[0], data[1], txn=txn)
-            txn.commit()
-    except EOFError:
-        backup_table.close()
-
-def __build_tbl_map(database):
-    """
-    Builds a table map of names to database tables.
-
-    :param database: database instance to backup
-    :type database: DbDir
-    """
-    return [
-        ( PERSON_TBL,  database.person_map.db),
-        ( FAMILY_TBL,  database.family_map.db),
-        ( PLACES_TBL,  database.place_map.db),
-        ( SOURCES_TBL, database.source_map.db),
-        ( CITATIONS_TBL, database.citation_map.db),
-        ( REPO_TBL,    database.repository_map.db),
-        ( NOTE_TBL,    database.note_map.db),
-        ( MEDIA_TBL,   database.media_map.db),
-        ( EVENTS_TBL,  database.event_map.db),
-        ( TAG_TBL,     database.tag_map.db),
-        ( META,        database.metadata.db),
-        ]
diff --git a/gramps/plugins/database/bsddb_support/summary.py b/gramps/plugins/database/bsddb_support/summary.py
new file mode 100644
index 000000000..ccc94d2dc
--- /dev/null
+++ b/gramps/plugins/database/bsddb_support/summary.py
@@ -0,0 +1,62 @@
+## Removed from clidbman.py
+## specific to bsddb
+
+from bsddb3 import dbshelve, db
+import os
+
+from gramps.gen.db import META, PERSON_TBL
+from  gramps.gen.db.dbconst import BDBVERSFN
+
+def get_dbdir_summary(dirpath, name):
+    """
+    Returns (people_count, bsddb_version, schema_version) of
+    current DB.
+    Returns ("Unknown", "Unknown", "Unknown") if invalid DB or other error.
+    """
+
+    bdbversion_file = os.path.join(dirpath, BDBVERSFN)
+    if os.path.isfile(bdbversion_file):
+        vers_file = open(bdbversion_file)
+        bsddb_version = vers_file.readline().strip()
+    else:
+        return "Unknown", "Unknown", "Unknown"
+        
+    current_bsddb_version = str(db.version())
+    if bsddb_version != current_bsddb_version:
+        return "Unknown", bsddb_version, "Unknown"
+        
+    env = db.DBEnv()
+    flags = db.DB_CREATE | db.DB_PRIVATE |\
+        db.DB_INIT_MPOOL |\
+        db.DB_INIT_LOG | db.DB_INIT_TXN
+    try:
+        env.open(dirpath, flags)
+    except Exception as msg:
+        LOG.warning("Error opening db environment for '%s': %s" %
+                    (name, str(msg)))
+        try:
+            env.close()
+        except Exception as msg:
+            LOG.warning("Error closing db environment for '%s': %s" %
+                    (name, str(msg)))
+        return "Unknown", bsddb_version, "Unknown"
+    dbmap1 = dbshelve.DBShelf(env)
+    fname = os.path.join(dirpath, META + ".db")
+    try:
+        dbmap1.open(fname, META, db.DB_HASH, db.DB_RDONLY)
+    except:
+        env.close()
+        return "Unknown", bsddb_version, "Unknown"
+    schema_version = dbmap1.get(b'version', default=None)
+    dbmap1.close()
+    dbmap2 = dbshelve.DBShelf(env)
+    fname = os.path.join(dirpath, PERSON_TBL + ".db")
+    try:
+        dbmap2.open(fname, PERSON_TBL, db.DB_HASH, db.DB_RDONLY)
+    except:
+        env.close()
+        return "Unknown", bsddb_version, schema_version
+    count = len(dbmap2)
+    dbmap2.close()
+    env.close()
+    return (count, bsddb_version, schema_version)
diff --git a/gramps/gen/db/test/__init__.py b/gramps/plugins/database/bsddb_support/test/__init__.py
similarity index 100%
rename from gramps/gen/db/test/__init__.py
rename to gramps/plugins/database/bsddb_support/test/__init__.py
diff --git a/gramps/gen/db/test/cursor_test.py b/gramps/plugins/database/bsddb_support/test/cursor_test.py
similarity index 100%
rename from gramps/gen/db/test/cursor_test.py
rename to gramps/plugins/database/bsddb_support/test/cursor_test.py
diff --git a/gramps/gen/db/test/db_test.py b/gramps/plugins/database/bsddb_support/test/db_test.py
similarity index 100%
rename from gramps/gen/db/test/db_test.py
rename to gramps/plugins/database/bsddb_support/test/db_test.py
diff --git a/gramps/gen/db/test/grampsdbtestbase.py b/gramps/plugins/database/bsddb_support/test/grampsdbtestbase.py
similarity index 100%
rename from gramps/gen/db/test/grampsdbtestbase.py
rename to gramps/plugins/database/bsddb_support/test/grampsdbtestbase.py
diff --git a/gramps/gen/db/test/reference_map_test.py b/gramps/plugins/database/bsddb_support/test/reference_map_test.py
similarity index 100%
rename from gramps/gen/db/test/reference_map_test.py
rename to gramps/plugins/database/bsddb_support/test/reference_map_test.py
diff --git a/gramps/plugins/database/bsddb_support/write.py b/gramps/plugins/database/bsddb_support/write.py
index 55dcf771d..42a5ad138 100644
--- a/gramps/plugins/database/bsddb_support/write.py
+++ b/gramps/plugins/database/bsddb_support/write.py
@@ -40,16 +40,12 @@ from functools import wraps
 import logging
 from sys import maxsize, getfilesystemencoding, version_info
 
-try:
-    from bsddb3 import dbshelve, db
-except:
-    # FIXME: make this more abstract to deal with other backends
-    class db:
-        DB_HASH = 0
-        DBRunRecoveryError = 0
-        DBAccessError = 0
-        DBPageNotFoundError = 0
-        DBInvalidArgError = 0
+from bsddb3 import dbshelve, db
+from bsddb3.db import DB_CREATE, DB_AUTO_COMMIT, DB_DUP, DB_DUPSORT, DB_RDONLY
+
+DBFLAGS_O = DB_CREATE | DB_AUTO_COMMIT  # Default flags for database open
+DBFLAGS_R = DB_RDONLY                   # Flags to open a database read-only
+DBFLAGS_D = DB_DUP | DB_DUPSORT         # Default flags for duplicate keys
 
 #-------------------------------------------------------------------------
 #
@@ -2448,6 +2444,146 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
         """
         return DbTxn
 
+    def backup(self):
+        """
+        Exports the database to a set of backup files. These files consist
+        of the pickled database tables, one file for each table.
+        
+        The heavy lifting is done by the private :py:func:`__do__export` function.
+        The purpose of this function is to catch any exceptions that occur.
+        
+        :param database: database instance to backup
+        :type database: DbDir
+        """
+        try:
+            do_export(self)
+        except (OSError, IOError) as msg:
+            raise DbException(str(msg))
+
+    def restore(self):
+        """
+        Restores the database to a set of backup files. These files consist
+        of the pickled database tables, one file for each table.
+        
+        The heavy lifting is done by the private :py:func:`__do__restore` function.
+        The purpose of this function is to catch any exceptions that occur.
+        
+        :param database: database instance to restore
+        :type database: DbDir
+        """
+        try:
+            do_restore(self)
+        except (OSError, IOError) as msg:
+            raise DbException(str(msg))
+
+def mk_backup_name(database, base):
+    """
+    Return the backup name of the database table
+
+    :param database: database instance 
+    :type database: DbDir
+    :param base: base name of the table
+    :type base: str
+    """
+    return os.path.join(database.get_save_path(), base + ".gbkp")
+
+def mk_tmp_name(database, base):
+    """
+    Return the temporary backup name of the database table
+
+    :param database: database instance 
+    :type database: DbDir
+    :param base: base name of the table
+    :type base: str
+    """
+    return os.path.join(database.get_save_path(), base + ".gbkp.new")
+
+def do_export(database):
+    """
+    Loop through each table of the database, saving the pickled data
+    a file.
+
+    :param database: database instance to backup
+    :type database: DbDir
+    """
+    try:
+        for (base, tbl) in build_tbl_map(database):
+            backup_name = mk_tmp_name(database, base)
+            backup_table = open(backup_name, 'wb')
+    
+            cursor = tbl.cursor()
+            data = cursor.first()
+            while data:
+                pickle.dump(data, backup_table, 2)
+                data = cursor.next()
+            cursor.close()
+            backup_table.close()
+    except (IOError,OSError):
+        return
+
+    for (base, tbl) in build_tbl_map(database):
+        new_name = mk_backup_name(database, base)
+        old_name = mk_tmp_name(database, base)
+        if os.path.isfile(new_name):
+            os.unlink(new_name)
+        os.rename(old_name, new_name)
+
+def do_restore(database):
+    """
+    Loop through each table of the database, restoring the pickled data
+    to the appropriate database file.
+
+    :param database: database instance to backup
+    :type database: DbDir
+    """
+    for (base, tbl) in build_tbl_map(database):
+        backup_name = mk_backup_name(database, base)
+        backup_table = open(backup_name, 'rb')
+        load_tbl_txn(database, backup_table, tbl)
+
+    database.rebuild_secondary()
+
+def load_tbl_txn(database, backup_table, tbl):
+    """
+    Return the temporary backup name of the database table
+
+    :param database: database instance 
+    :type database: DbDir
+    :param backup_table: file containing the backup data
+    :type backup_table: file
+    :param tbl: Berkeley db database table
+    :type tbl: Berkeley db database table
+    """
+    try:
+        while True:
+            data = pickle.load(backup_table)
+            txn = database.env.txn_begin()
+            tbl.put(data[0], data[1], txn=txn)
+            txn.commit()
+    except EOFError:
+        backup_table.close()
+
+def build_tbl_map(database):
+    """
+    Builds a table map of names to database tables.
+
+    :param database: database instance to backup
+    :type database: DbDir
+    """
+    return [
+        ( PERSON_TBL,  database.person_map.db),
+        ( FAMILY_TBL,  database.family_map.db),
+        ( PLACES_TBL,  database.place_map.db),
+        ( SOURCES_TBL, database.source_map.db),
+        ( CITATIONS_TBL, database.citation_map.db),
+        ( REPO_TBL,    database.repository_map.db),
+        ( NOTE_TBL,    database.note_map.db),
+        ( MEDIA_TBL,   database.media_map.db),
+        ( EVENTS_TBL,  database.event_map.db),
+        ( TAG_TBL,     database.tag_map.db),
+        ( META,        database.metadata.db),
+        ]
+
 def _mkname(path, name):
     return os.path.join(path, name + DBEXT)
 
diff --git a/gramps/plugins/database/bsddb_support/dictionary.py b/gramps/plugins/database/dictionary.py
similarity index 100%
rename from gramps/plugins/database/bsddb_support/dictionary.py
rename to gramps/plugins/database/dictionary.py
diff --git a/gramps/plugins/gramplet/leak.py b/gramps/plugins/gramplet/leak.py
index 58ae93bd5..82d479822 100644
--- a/gramps/plugins/gramplet/leak.py
+++ b/gramps/plugins/gramplet/leak.py
@@ -31,7 +31,6 @@ Show uncollected objects in a window.
 #------------------------------------------------------------------------
 from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
-from bsddb3.db import DBError
 
 #------------------------------------------------------------------------
 #
@@ -156,6 +155,13 @@ class Leak(Gramplet):
                         parent=self.uistate.window)
 
     def display(self):
+        try:
+            from bsddb3.db import DBError
+        except:
+            class DBError(Exception):
+                """
+                Dummy.
+                """
         gc.collect(2)
         self.model.clear()
         count = 0

From 81ebdd97ccffc43acbb9f70936093c58789af26c Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Tue, 12 May 2015 23:08:54 -0400
Subject: [PATCH 004/105] Database backend writes its plugin id in database.txt

---
 gramps/gen/db/dbconst.py                       | 4 +++-
 gramps/plugins/database/bsddb_support/write.py | 5 +++++
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/gramps/gen/db/dbconst.py b/gramps/gen/db/dbconst.py
index 7b91ab7e6..e53535248 100644
--- a/gramps/gen/db/dbconst.py
+++ b/gramps/gen/db/dbconst.py
@@ -31,7 +31,8 @@ Declare constants used by database modules
 __all__ = (
             ('DBPAGE', 'DBMODE', 'DBCACHE', 'DBLOCKS', 'DBOBJECTS', 'DBUNDO',
              'DBEXT', 'DBMODE_R', 'DBMODE_W', 'DBUNDOFN', 'DBLOCKFN',
-             'DBRECOVFN','BDBVERSFN', 'DBLOGNAME', 'SCHVERSFN', 'PCKVERSFN'
+             'DBRECOVFN','BDBVERSFN', 'DBLOGNAME', 'SCHVERSFN', 'PCKVERSFN',
+             'DBBACKEND'
             ) +
             
             ('PERSON_KEY', 'FAMILY_KEY', 'SOURCE_KEY', 'CITATION_KEY',
@@ -47,6 +48,7 @@ DBUNDOFN  = "undo.db"       # File name of 'undo' database
 DBLOCKFN  = "lock"          # File name of lock file
 DBRECOVFN = "need_recover"  # File name of recovery file
 BDBVERSFN = "bdbversion.txt"# File name of Berkeley DB version file
+DBBACKEND = "database.txt"  # File name of Database backend file
 SCHVERSFN = "schemaversion.txt"# File name of schema version file
 PCKVERSFN = "pickleupgrade.txt" # Indicator that pickle has been upgrade t Python3
 DBLOGNAME = ".Db"           # Name of logger
diff --git a/gramps/plugins/database/bsddb_support/write.py b/gramps/plugins/database/bsddb_support/write.py
index 42a5ad138..920bd2938 100644
--- a/gramps/plugins/database/bsddb_support/write.py
+++ b/gramps/plugins/database/bsddb_support/write.py
@@ -2428,6 +2428,11 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
             version = str(_DBVERSION)
             version_file.write(version)
 
+        versionpath = os.path.join(name, str(DBBACKEND))
+        _LOG.debug("Write database backend file to 'bsddb'")
+        with open(versionpath, "w") as version_file:
+            version_file.write("bsddb")
+
         self.metadata.close()
         self.env.close()
   

From 16a5665e3e871dc7e96ebc5bf7278c3dd0c3be96 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Wed, 13 May 2015 08:09:30 -0400
Subject: [PATCH 005/105] Added Django and Dictionary plugins, to be developed

---
 gramps/plugins/database/dbdjango.py           | 2036 ++++++++++++++++
 gramps/plugins/database/dictionarydb.gpr.py   |   31 +
 .../{dictionary.py => dictionarydb.py}        |   22 +-
 .../database/django_support/libdjango.py      | 2063 +++++++++++++++++
 gramps/plugins/database/djangodb.gpr.py       |   31 +
 gramps/plugins/database/djangodb.py           | 2036 ++++++++++++++++
 6 files changed, 6208 insertions(+), 11 deletions(-)
 create mode 100644 gramps/plugins/database/dbdjango.py
 create mode 100644 gramps/plugins/database/dictionarydb.gpr.py
 rename gramps/plugins/database/{dictionary.py => dictionarydb.py} (98%)
 create mode 100644 gramps/plugins/database/django_support/libdjango.py
 create mode 100644 gramps/plugins/database/djangodb.gpr.py
 create mode 100644 gramps/plugins/database/djangodb.py

diff --git a/gramps/plugins/database/dbdjango.py b/gramps/plugins/database/dbdjango.py
new file mode 100644
index 000000000..adb28dc54
--- /dev/null
+++ b/gramps/plugins/database/dbdjango.py
@@ -0,0 +1,2036 @@
+# Gramps - a GTK+/GNOME based genealogy program
+#
+# Copyright (C) 2009         Douglas S. Blank <doug.blank@gmail.com>
+#
+# 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+""" Implements a Db interface """
+
+#------------------------------------------------------------------------
+#
+# Python Modules
+#
+#------------------------------------------------------------------------
+import sys
+import time
+import re
+import base64
+import pickle
+import os
+
+#------------------------------------------------------------------------
+#
+# Gramps Modules
+#
+#------------------------------------------------------------------------
+import gramps
+from gramps.gen.lib import (Person, Family, Event, Place, Repository, 
+                            Citation, Source, Note, MediaObject, Tag, 
+                            Researcher)
+from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn
+from gramps.gen.utils.callback import Callback
+from gramps.gen.updatecallback import UpdateCallback
+from gramps.gen.db import (PERSON_KEY,
+                    FAMILY_KEY,
+                    CITATION_KEY,
+                    SOURCE_KEY,
+                    EVENT_KEY,
+                    MEDIA_KEY,
+                    PLACE_KEY,
+                    REPOSITORY_KEY,
+                    NOTE_KEY)
+from gramps.gen.utils.id import create_id
+from django.db import transaction
+
+class Environment(object):
+    """
+    Implements the Environment API.
+    """
+    def __init__(self, db):
+        self.db = db
+
+    def txn_begin(self):
+        return DjangoTxn("DbDjango Transaction", self.db)
+
+class Table(object):
+    """
+    Implements Table interface.
+    """
+    def __init__(self, funcs):
+        self.funcs = funcs
+
+    def cursor(self):
+        """
+        Returns a Cursor for this Table.
+        """
+        return self.funcs["cursor_func"]()
+
+    def put(key, data, txn=None):
+        self[key] = data
+
+class Map(dict):
+    """
+    Implements the map API for person_map, etc.
+    
+    Takes a Table() as argument.
+    """
+    def __init__(self, tbl, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.db = tbl
+
+class MetaCursor(object):
+    def __init__(self):
+        pass
+    def __enter__(self):
+        return self
+    def __iter__(self):
+        return self.__next__()
+    def __next__(self):
+        yield None
+    def __exit__(self, *args, **kwargs):
+        pass
+    def iter(self):
+        yield None
+    def first(self):
+        self._iter = self.__iter__()
+        return self.next()
+    def next(self):
+        try:
+            return next(self._iter)
+        except:
+            return None
+    def close(self):
+        pass
+
+class Cursor(object):
+    def __init__(self, model, func):
+        self.model = model
+        self.func = func
+    def __enter__(self):
+        return self
+    def __iter__(self):
+        return self.__next__()
+    def __next__(self):
+        for item in self.model.all():
+            yield (bytes(item.handle, "utf-8"), self.func(item.handle))
+    def __exit__(self, *args, **kwargs):
+        pass
+    def iter(self):
+        for item in self.model.all():
+            yield (bytes(item.handle, "utf-8"), self.func(item.handle))
+        yield None
+    def first(self):
+        self._iter = self.__iter__()
+        return self.next()
+    def next(self):
+        try:
+            return next(self._iter)
+        except:
+            return None
+    def close(self):
+        pass
+
+class Bookmarks(object):
+    def __init__(self):
+        self.handles = []
+    def get(self):
+        return self.handles
+    def append(self, handle):
+        self.handles.append(handle)
+
+class DjangoTxn(DbTxn):
+    def __init__(self, message, db, table=None):
+        DbTxn.__init__(self, message, db)
+        self.table = table
+
+    def get(self, key, default=None, txn=None, **kwargs):
+        """
+        Returns the data object associated with key
+        """
+        try:
+            return self.table.objects(handle=key)
+        except:
+            if txn and key in txn:
+                return txn[key]
+            else:
+                return None
+
+    def put(self, handle, new_data, txn):
+        """
+        """
+        txn[handle] = new_data
+
+    def commit(self):
+        pass
+
+class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
+    """
+    A Gramps Database Backend. This replicates the grampsdb functions.
+    """
+    # Set up dictionary for callback signal handler
+    # ---------------------------------------------
+    # 1. Signals for primary objects
+    __signals__ = dict((obj+'-'+op, signal)
+                       for obj in
+                       ['person', 'family', 'event', 'place',
+                        'source', 'citation', 'media', 'note', 'repository', 'tag']
+                       for op, signal in zip(
+                               ['add',   'update', 'delete', 'rebuild'],
+                               [(list,), (list,),  (list,),   None]
+                       )
+                   )
+    
+    # 2. Signals for long operations
+    __signals__.update(('long-op-'+op, signal) for op, signal in zip(
+        ['start',  'heartbeat', 'end'],
+        [(object,), None,       None]
+        ))
+
+    # 3. Special signal for change in home person
+    __signals__['home-person-changed'] = None
+
+    # 4. Signal for change in person group name, parameters are
+    __signals__['person-groupname-rebuild'] = (str, str)
+
+    def __init__(self, directory=None):
+        DbReadBase.__init__(self)
+        DbWriteBase.__init__(self)
+        Callback.__init__(self)
+        self._tables = {
+            'Person':
+                {
+                "handle_func": self.get_person_from_handle, 
+                "gramps_id_func": self.get_person_from_gramps_id,
+                "class_func": gramps.gen.lib.Person,
+                "cursor_func": self.get_person_cursor,
+                "handles_func": self.get_person_handles,
+                "iter_func": self.iter_people,
+                },
+            'Family':
+                {
+                "handle_func": self.get_family_from_handle, 
+                "gramps_id_func": self.get_family_from_gramps_id,
+                "class_func": gramps.gen.lib.Family,
+                "cursor_func": self.get_family_cursor,
+                "handles_func": self.get_family_handles,
+                "iter_func": self.iter_families,
+                },
+            'Source':
+                {
+                "handle_func": self.get_source_from_handle, 
+                "gramps_id_func": self.get_source_from_gramps_id,
+                "class_func": gramps.gen.lib.Source,
+                "cursor_func": self.get_source_cursor,
+                "handles_func": self.get_source_handles,
+                "iter_func": self.iter_sources,
+                },
+            'Citation':
+                {
+                "handle_func": self.get_citation_from_handle, 
+                "gramps_id_func": self.get_citation_from_gramps_id,
+                "class_func": gramps.gen.lib.Citation,
+                "cursor_func": self.get_citation_cursor,
+                "handles_func": self.get_citation_handles,
+                "iter_func": self.iter_citations,
+                },
+            'Event':
+                {
+                "handle_func": self.get_event_from_handle, 
+                "gramps_id_func": self.get_event_from_gramps_id,
+                "class_func": gramps.gen.lib.Event,
+                "cursor_func": self.get_event_cursor,
+                "handles_func": self.get_event_handles,
+                "iter_func": self.iter_events,
+                },
+            'Media':
+                {
+                "handle_func": self.get_object_from_handle, 
+                "gramps_id_func": self.get_object_from_gramps_id,
+                "class_func": gramps.gen.lib.MediaObject,
+                "cursor_func": self.get_media_cursor,
+                "handles_func": self.get_media_object_handles,
+                "iter_func": self.iter_media_objects,
+                },
+            'Place':
+                {
+                "handle_func": self.get_place_from_handle, 
+                "gramps_id_func": self.get_place_from_gramps_id,
+                "class_func": gramps.gen.lib.Place,
+                "cursor_func": self.get_place_cursor,
+                "handles_func": self.get_place_handles,
+                "iter_func": self.iter_places,
+                },
+            'Repository':
+                {
+                "handle_func": self.get_repository_from_handle, 
+                "gramps_id_func": self.get_repository_from_gramps_id,
+                "class_func": gramps.gen.lib.Repository,
+                "cursor_func": self.get_repository_cursor,
+                "handles_func": self.get_repository_handles,
+                "iter_func": self.iter_repositories,
+                },
+            'Note':
+                {
+                "handle_func": self.get_note_from_handle, 
+                "gramps_id_func": self.get_note_from_gramps_id,
+                "class_func": gramps.gen.lib.Note,
+                "cursor_func": self.get_note_cursor,
+                "handles_func": self.get_note_handles,
+                "iter_func": self.iter_notes,
+                },
+            'Tag':
+                {
+                "handle_func": self.get_tag_from_handle, 
+                "gramps_id_func": None,
+                "class_func": gramps.gen.lib.Tag,
+                "cursor_func": self.get_tag_cursor,
+                "handles_func": self.get_tag_handles,
+                "iter_func": self.iter_tags,
+                },
+            }
+        # skip GEDCOM cross-ref check for now:
+        self.set_feature("skip-check-xref", True)
+        self.readonly = False
+        self.db_is_open = True
+        self.name_formats = []
+        self.bookmarks = Bookmarks()
+        self.undo_callback = None
+        self.redo_callback = None
+        self.undo_history_callback = None
+        self.modified   = 0
+        self.txn = DjangoTxn("DbDjango Transaction", self)
+        self.transaction = None
+        # Import cache for gedcom import, uses transactions, and
+        # two step adding of objects.
+        self.import_cache = {}
+        self.use_import_cache = False
+        self.use_db_cache = True
+        self._directory = directory
+        if directory:
+            self.load(directory)
+
+    def load(self, directory, pulse_progress=None, mode=None):
+        self._directory = directory
+        from django.conf import settings
+        default_settings = {}
+        settings_file = os.path.join(directory, "default_settings.py")
+        with open(settings_file) as f:
+            code = compile(f.read(), settings_file, 'exec')
+            exec(code, globals(), default_settings)
+
+        class Module(object):
+            def __init__(self, dictionary):
+                self.dictionary = dictionary
+            def __getattr__(self, item):
+                return self.dictionary[item]
+
+        try:
+            settings.configure(Module(default_settings))
+        except RuntimeError:
+            # already configured; ignore
+            pass
+
+        import django
+        django.setup()
+
+        from django_support.libdjango import DjangoInterface
+        self.dji = DjangoInterface()
+        self.family_bookmarks = Bookmarks()
+        self.event_bookmarks = Bookmarks()
+        self.place_bookmarks = Bookmarks()
+        self.citation_bookmarks = Bookmarks()
+        self.source_bookmarks = Bookmarks()
+        self.repo_bookmarks = Bookmarks()
+        self.media_bookmarks = Bookmarks()
+        self.note_bookmarks = Bookmarks()
+        self.set_person_id_prefix('I%04d')
+        self.set_object_id_prefix('O%04d')
+        self.set_family_id_prefix('F%04d')
+        self.set_citation_id_prefix('C%04d')
+        self.set_source_id_prefix('S%04d')
+        self.set_place_id_prefix('P%04d')
+        self.set_event_id_prefix('E%04d')
+        self.set_repository_id_prefix('R%04d')
+        self.set_note_id_prefix('N%04d')
+        # ----------------------------------
+        self.id_trans  = DjangoTxn("ID Transaction", self, self.dji.Person)
+        self.fid_trans = DjangoTxn("FID Transaction", self, self.dji.Family)
+        self.pid_trans = DjangoTxn("PID Transaction", self, self.dji.Place)
+        self.cid_trans = DjangoTxn("CID Transaction", self, self.dji.Citation)
+        self.sid_trans = DjangoTxn("SID Transaction", self, self.dji.Source)
+        self.oid_trans = DjangoTxn("OID Transaction", self, self.dji.Media)
+        self.rid_trans = DjangoTxn("RID Transaction", self, self.dji.Repository)
+        self.nid_trans = DjangoTxn("NID Transaction", self, self.dji.Note)
+        self.eid_trans = DjangoTxn("EID Transaction", self, self.dji.Event)
+        self.cmap_index = 0
+        self.smap_index = 0
+        self.emap_index = 0
+        self.pmap_index = 0
+        self.fmap_index = 0
+        self.lmap_index = 0
+        self.omap_index = 0
+        self.rmap_index = 0
+        self.nmap_index = 0
+        self.env = Environment(self)
+        self.person_map = Map(Table(self._tables["Person"]))
+        self.family_map = Map(Table(self._tables["Family"]))
+        self.place_map  = Map(Table(self._tables["Place"]))
+        self.citation_map = Map(Table(self._tables["Citation"]))
+        self.source_map = Map(Table(self._tables["Source"]))
+        self.repository_map  = Map(Table(self._tables["Repository"]))
+        self.note_map = Map(Table(self._tables["Note"]))
+        self.media_map  = Map(Table(self._tables["Media"]))
+        self.event_map  = Map(Table(self._tables["Event"]))
+        self.tag_map  = Map(Table(self._tables["Tag"]))
+        self.metadata   = Map(Table({"cursor_func": lambda: MetaCursor()}))
+        self.name_group = {}
+        self.event_names = set()
+        self.individual_attributes = set()
+        self.family_attributes = set()
+        self.source_attributes = set()
+        self.child_ref_types = set()
+        self.family_rel_types = set()
+        self.event_role_names = set()
+        self.name_types = set()
+        self.origin_types = set()
+        self.repository_types = set()
+        self.note_types = set()
+        self.source_media_types = set()
+        self.url_types = set()
+        self.media_attributes = set()
+        self.place_types = set()
+
+    def prepare_import(self):
+        """
+        DbDjango does not commit data on gedcom import, but saves them
+        for later commit.
+        """
+        self.use_import_cache = True
+        self.import_cache = {}
+
+    @transaction.atomic
+    def commit_import(self):
+        """
+        Commits the items that were queued up during the last gedcom
+        import for two step adding.
+        """
+        # First we add the primary objects:
+        for key in list(self.import_cache.keys()):
+            obj = self.import_cache[key]
+            if isinstance(obj, Person):
+                self.dji.add_person(obj.serialize())
+            elif isinstance(obj, Family):
+                self.dji.add_family(obj.serialize())
+            elif isinstance(obj, Event):
+                self.dji.add_event(obj.serialize())
+            elif isinstance(obj, Place):
+                self.dji.add_place(obj.serialize())
+            elif isinstance(obj, Repository):
+                self.dji.add_repository(obj.serialize())
+            elif isinstance(obj, Citation):
+                self.dji.add_citation(obj.serialize())
+            elif isinstance(obj, Source):
+                self.dji.add_source(obj.serialize())
+            elif isinstance(obj, Note):
+                self.dji.add_note(obj.serialize())
+            elif isinstance(obj, MediaObject):
+                self.dji.add_media(obj.serialize())
+            elif isinstance(obj, Tag):
+                self.dji.add_tag(obj.serialize())
+        # Next we add the links:
+        for key in list(self.import_cache.keys()):
+            obj = self.import_cache[key]
+            if isinstance(obj, Person):
+                self.dji.add_person_detail(obj.serialize())
+            elif isinstance(obj, Family):
+                self.dji.add_family_detail(obj.serialize())
+            elif isinstance(obj, Event):
+                self.dji.add_event_detail(obj.serialize())
+            elif isinstance(obj, Place):
+                self.dji.add_place_detail(obj.serialize())
+            elif isinstance(obj, Repository):
+                self.dji.add_repository_detail(obj.serialize())
+            elif isinstance(obj, Citation):
+                self.dji.add_citation_detail(obj.serialize())
+            elif isinstance(obj, Source):
+                self.dji.add_source_detail(obj.serialize())
+            elif isinstance(obj, Note):
+                self.dji.add_note_detail(obj.serialize())
+            elif isinstance(obj, MediaObject):
+                self.dji.add_media_detail(obj.serialize())
+            elif isinstance(obj, Tag):
+                self.dji.add_tag_detail(obj.serialize())
+        self.use_import_cache = False
+        self.import_cache = {}
+        self.dji.update_publics()
+
+    def transaction_commit(self, txn):
+        pass
+
+    def request_rebuild(self):
+        # caches are ok, but let's compute public's
+        self.dji.update_publics()
+        self.emit('person-rebuild')
+        self.emit('family-rebuild')
+        self.emit('place-rebuild')
+        self.emit('source-rebuild')
+        self.emit('citation-rebuild')
+        self.emit('media-rebuild')
+        self.emit('event-rebuild')
+        self.emit('repository-rebuild')
+        self.emit('note-rebuild')
+        self.emit('tag-rebuild')
+
+    def get_undodb(self):
+        return None
+
+    def transaction_abort(self, txn):
+        pass
+
+    @staticmethod
+    def _validated_id_prefix(val, default):
+        if isinstance(val, str) and val:
+            try:
+                str_ = val % 1
+            except TypeError:           # missing conversion specifier
+                prefix_var = val + "%d"
+            except ValueError:          # incomplete format
+                prefix_var = default+"%04d"
+            else:
+                prefix_var = val        # OK as given
+        else:
+            prefix_var = default+"%04d" # not a string or empty string
+        return prefix_var
+
+    @staticmethod
+    def __id2user_format(id_pattern):
+        """
+        Return a method that accepts a Gramps ID and adjusts it to the users
+        format.
+        """
+        pattern_match = re.match(r"(.*)%[0 ](\d+)[diu]$", id_pattern)
+        if pattern_match:
+            str_prefix = pattern_match.group(1)
+            nr_width = pattern_match.group(2)
+            def closure_func(gramps_id):
+                if gramps_id and gramps_id.startswith(str_prefix):
+                    id_number = gramps_id[len(str_prefix):]
+                    if id_number.isdigit():
+                        id_value = int(id_number, 10)
+                        #if len(str(id_value)) > nr_width:
+                        #    # The ID to be imported is too large to fit in the
+                        #    # users format. For now just create a new ID,
+                        #    # because that is also what happens with IDs that
+                        #    # are identical to IDs already in the database. If
+                        #    # the problem of colliding import and already
+                        #    # present IDs is solved the code here also needs
+                        #    # some solution.
+                        #    gramps_id = id_pattern % 1
+                        #else:
+                        gramps_id = id_pattern % id_value
+                return gramps_id
+        else:
+            def closure_func(gramps_id):
+                return gramps_id
+        return closure_func
+
+    def set_person_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Person ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as I%d or I%04d.
+        """
+        self.person_prefix = self._validated_id_prefix(val, "I")
+        self.id2user_format = self.__id2user_format(self.person_prefix)
+
+    def set_citation_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Citation ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as C%d or C%04d.
+        """
+        self.citation_prefix = self._validated_id_prefix(val, "C")
+        self.cid2user_format = self.__id2user_format(self.citation_prefix)
+            
+    def set_source_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Source ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as S%d or S%04d.
+        """
+        self.source_prefix = self._validated_id_prefix(val, "S")
+        self.sid2user_format = self.__id2user_format(self.source_prefix)
+            
+    def set_object_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS MediaObject ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as O%d or O%04d.
+        """
+        self.mediaobject_prefix = self._validated_id_prefix(val, "O")
+        self.oid2user_format = self.__id2user_format(self.mediaobject_prefix)
+
+    def set_place_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Place ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as P%d or P%04d.
+        """
+        self.place_prefix = self._validated_id_prefix(val, "P")
+        self.pid2user_format = self.__id2user_format(self.place_prefix)
+
+    def set_family_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Family ID values. The string is
+        expected to be in the form of a simple text string, or in a format
+        that contains a C/Python style format string using %d, such as F%d
+        or F%04d.
+        """
+        self.family_prefix = self._validated_id_prefix(val, "F")
+        self.fid2user_format = self.__id2user_format(self.family_prefix)
+
+    def set_event_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Event ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as E%d or E%04d.
+        """
+        self.event_prefix = self._validated_id_prefix(val, "E")
+        self.eid2user_format = self.__id2user_format(self.event_prefix)
+
+    def set_repository_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Repository ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as R%d or R%04d.
+        """
+        self.repository_prefix = self._validated_id_prefix(val, "R")
+        self.rid2user_format = self.__id2user_format(self.repository_prefix)
+
+    def set_note_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Note ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as N%d or N%04d.
+        """
+        self.note_prefix = self._validated_id_prefix(val, "N")
+        self.nid2user_format = self.__id2user_format(self.note_prefix)
+
+    def __find_next_gramps_id(self, prefix, map_index, trans):
+        """
+        Helper function for find_next_<object>_gramps_id methods
+        """
+        index = prefix % map_index
+        while trans.get(str(index), txn=self.txn) is not None:
+            map_index += 1
+            index = prefix % map_index
+        map_index += 1
+        return (map_index, index)
+        
+    def find_next_person_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Person object based off the 
+        person ID prefix.
+        """
+        self.pmap_index, gid = self.__find_next_gramps_id(self.person_prefix,
+                                          self.pmap_index, self.id_trans)
+        return gid
+
+    def find_next_place_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Place object based off the 
+        place ID prefix.
+        """
+        self.lmap_index, gid = self.__find_next_gramps_id(self.place_prefix,
+                                          self.lmap_index, self.pid_trans)
+        return gid
+
+    def find_next_event_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Event object based off the 
+        event ID prefix.
+        """
+        self.emap_index, gid = self.__find_next_gramps_id(self.event_prefix,
+                                          self.emap_index, self.eid_trans)
+        return gid
+
+    def find_next_object_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a MediaObject object based
+        off the media object ID prefix.
+        """
+        self.omap_index, gid = self.__find_next_gramps_id(self.mediaobject_prefix,
+                                          self.omap_index, self.oid_trans)
+        return gid
+
+    def find_next_citation_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Citation object based off the 
+        citation ID prefix.
+        """
+        self.cmap_index, gid = self.__find_next_gramps_id(self.citation_prefix,
+                                          self.cmap_index, self.cid_trans)
+        return gid
+
+    def find_next_source_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Source object based off the 
+        source ID prefix.
+        """
+        self.smap_index, gid = self.__find_next_gramps_id(self.source_prefix,
+                                          self.smap_index, self.sid_trans)
+        return gid
+
+    def find_next_family_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Family object based off the 
+        family ID prefix.
+        """
+        self.fmap_index, gid = self.__find_next_gramps_id(self.family_prefix,
+                                          self.fmap_index, self.fid_trans)
+        return gid
+
+    def find_next_repository_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Respository object based 
+        off the repository ID prefix.
+        """
+        self.rmap_index, gid = self.__find_next_gramps_id(self.repository_prefix,
+                                          self.rmap_index, self.rid_trans)
+        return gid
+
+    def find_next_note_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Note object based off the 
+        note ID prefix.
+        """
+        self.nmap_index, gid = self.__find_next_gramps_id(self.note_prefix,
+                                          self.nmap_index, self.nid_trans)
+        return gid
+
+    def get_mediapath(self):
+        return None
+
+    def get_name_group_keys(self):
+        return []
+
+    def get_name_group_mapping(self, key):
+        return None
+
+    def get_researcher(self):
+        obj = Researcher()
+        return obj
+
+    def get_tag_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Tag.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Tag.all()]
+
+    def get_person_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Person.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Person.all()]
+
+    def get_family_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Family.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Family.all()]
+
+    def get_event_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Event.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Event.all()]
+
+    def get_citation_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Citation.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Citation.all()]
+
+    def get_source_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Source.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Source.all()]
+
+    def get_place_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Place.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Place.all()]
+
+    def get_repository_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Repository.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Repository.all()]
+
+    def get_media_object_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Media.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Media.all()]
+
+    def get_note_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Note.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Note.all()]
+
+    def get_media_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            media = self.dji.Media.get(handle=handle)
+        except:
+            return None
+        return self.make_media(media)
+
+    def get_event_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            event = self.dji.Event.get(handle=handle)
+        except:
+            return None
+        return self.make_event(event)
+
+    def get_family_from_handle(self, handle): 
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            family = self.dji.Family.get(handle=handle)
+        except:
+            return None
+        return self.make_family(family)
+
+    def get_repository_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            repository = self.dji.Repository.get(handle=handle)
+        except:
+            return None
+        return self.make_repository(repository)
+
+    def get_person_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            person = self.dji.Person.get(handle=handle)
+        except:
+            return None
+        return self.make_person(person)
+
+    def get_tag_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            tag = self.dji.Tag.get(handle=handle)
+        except:
+            return None
+        return self.make_tag(tag)
+
+    def make_repository(self, repository):
+        if self.use_db_cache and repository.cache:
+            data = repository.from_cache()
+        else:
+            data = self.dji.get_repository(repository)
+        return Repository.create(data)
+
+    def make_citation(self, citation):
+        if self.use_db_cache and citation.cache:
+            data = citation.from_cache()
+        else:
+            data = self.dji.get_citation(citation)
+        return Citation.create(data)
+
+    def make_source(self, source):
+        if self.use_db_cache and source.cache:
+            data = source.from_cache()
+        else:
+            data = self.dji.get_source(source)
+        return Source.create(data)
+
+    def make_family(self, family):
+        if self.use_db_cache and family.cache:
+            data = family.from_cache()
+        else:
+            data = self.dji.get_family(family)
+        return Family.create(data)
+
+    def make_person(self, person):
+        if self.use_db_cache and person.cache:
+            data = person.from_cache()
+        else:
+            data = self.dji.get_person(person)
+        return Person.create(data)
+
+    def make_event(self, event):
+        if self.use_db_cache and event.cache:
+            data = event.from_cache()
+        else:
+            data = self.dji.get_event(event)
+        return Event.create(data)
+
+    def make_note(self, note):
+        if self.use_db_cache and note.cache:
+            data = note.from_cache()
+        else:
+            data = self.dji.get_note(note)
+        return Note.create(data)
+
+    def make_tag(self, tag):
+        data = self.dji.get_tag(tag)
+        return Tag.create(data)
+
+    def make_place(self, place):
+        if self.use_db_cache and place.cache:
+            data = place.from_cache()
+        else:
+            data = self.dji.get_place(place)
+        return Place.create(data)
+
+    def make_media(self, media):
+        if self.use_db_cache and media.cache:
+            data = media.from_cache()
+        else:
+            data = self.dji.get_media(media)
+        return MediaObject.create(data)
+
+    def get_place_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            place = self.dji.Place.get(handle=handle)
+        except:
+            return None
+        return self.make_place(place)
+
+    def get_citation_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            citation = self.dji.Citation.get(handle=handle)
+        except:
+            return None
+        return self.make_citation(citation)
+
+    def get_source_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            source = self.dji.Source.get(handle=handle)
+        except:
+            return None
+        return self.make_source(source)
+
+    def get_note_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            note = self.dji.Note.get(handle=handle)
+        except:
+            return None
+        return self.make_note(note)
+
+    def get_object_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            media = self.dji.Media.get(handle=handle)
+        except:
+            return None
+        return self.make_media(media)
+
+    def get_default_person(self):
+        people = self.dji.Person.all()
+        if people.count() > 0:
+            return self.make_person(people[0])
+        return None
+
+    def iter_people(self):
+        return (self.get_person_from_handle(person.handle) 
+                for person in self.dji.Person.all())
+
+    def iter_person_handles(self):
+        return (person.handle for person in self.dji.Person.all())
+
+    def iter_families(self):
+        return (self.get_family_from_handle(family.handle) 
+                for family in self.dji.Family.all())
+
+    def iter_family_handles(self):
+        return (family.handle for family in self.dji.Family.all())
+
+    def iter_notes(self):
+        return (self.get_note_from_handle(note.handle) 
+                for note in self.dji.Note.all())
+
+    def iter_note_handles(self):
+        return (note.handle for note in self.dji.Note.all())
+
+    def iter_events(self):
+        return (self.get_event_from_handle(event.handle) 
+                for event in self.dji.Event.all())
+
+    def iter_event_handles(self):
+        return (event.handle for event in self.dji.Event.all())
+
+    def iter_places(self):
+        return (self.get_place_from_handle(place.handle) 
+                for place in self.dji.Place.all())
+
+    def iter_place_handles(self):
+        return (place.handle for place in self.dji.Place.all())
+
+    def iter_repositories(self):
+        return (self.get_repository_from_handle(repository.handle) 
+                for repository in self.dji.Repository.all())
+
+    def iter_repository_handles(self):
+        return (repository.handle for repository in self.dji.Repository.all())
+
+    def iter_sources(self):
+        return (self.get_source_from_handle(source.handle) 
+                for source in self.dji.Source.all())
+
+    def iter_source_handles(self):
+        return (source.handle for source in self.dji.Source.all())
+
+    def iter_citations(self):
+        return (self.get_citation_from_handle(citation.handle) 
+                for citation in self.dji.Citation.all())
+
+    def iter_citation_handles(self):
+        return (citation.handle for citation in self.dji.Citation.all())
+
+    def iter_tags(self):
+        return (self.get_tag_from_handle(tag.handle) 
+                for tag in self.dji.Tag.all())
+
+    def iter_tag_handles(self):
+        return (tag.handle for tag in self.dji.Tag.all())
+
+    def iter_media_objects(self):
+        return (self.get_media_from_handle(media.handle) 
+                for media in self.dji.Media.all())
+
+    def get_tag_from_name(self, name):
+        try:
+            tag = self.dji.Tag.filter(name=name)
+            return self.make_tag(tag[0])
+        except:
+            return None
+
+    def get_person_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Person.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_person(match_list[0])
+        else:
+            return None
+
+    def get_family_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        try:
+            family = self.dji.Family.get(gramps_id=gramps_id)
+        except:
+            return None
+        return self.make_family(family)
+
+    def get_source_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Source.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_source(match_list[0])
+        else:
+            return None
+
+    def get_citation_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Citation.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_citation(match_list[0])
+        else:
+            return None
+
+    def get_event_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Event.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_event(match_list[0])
+        else:
+            return None
+
+    def get_object_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Media.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_media(match_list[0])
+        else:
+            return None
+
+    def get_place_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Place.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_place(match_list[0])
+        else:
+            return None
+
+    def get_repository_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Repsoitory.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_repository(match_list[0])
+        else:
+            return None
+
+    def get_note_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Note.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_note(match_list[0])
+        else:
+            return None
+
+    def get_number_of_people(self):
+        return self.dji.Person.count()
+
+    def get_number_of_events(self):
+        return self.dji.Event.count()
+
+    def get_number_of_places(self):
+        return self.dji.Place.count()
+
+    def get_number_of_tags(self):
+        return self.dji.Tag.count()
+
+    def get_number_of_families(self):
+        return self.dji.Family.count()
+
+    def get_number_of_notes(self):
+        return self.dji.Note.count()
+
+    def get_number_of_citations(self):
+        return self.dji.Citation.count()
+
+    def get_number_of_sources(self):
+        return self.dji.Source.count()
+
+    def get_number_of_media_objects(self):
+        return self.dji.Media.count()
+
+    def get_number_of_repositories(self):
+        return self.dji.Repository.count()
+
+    def get_place_cursor(self):
+        return Cursor(self.dji.Place, self.get_raw_place_data)
+
+    def get_person_cursor(self):
+        return Cursor(self.dji.Person, self.get_raw_person_data)
+
+    def get_family_cursor(self):
+        return Cursor(self.dji.Family, self.get_raw_family_data)
+
+    def get_event_cursor(self):
+        return Cursor(self.dji.Event, self.get_raw_event_data)
+
+    def get_citation_cursor(self):
+        return Cursor(self.dji.Citation, self.get_raw_citation_data)
+
+    def get_source_cursor(self):
+        return Cursor(self.dji.Source, self.get_raw_source_data)
+
+    def get_note_cursor(self):
+        return Cursor(self.dji.Note, self.get_raw_note_data)
+
+    def get_tag_cursor(self):
+        return Cursor(self.dji.Tag, self.get_raw_tag_data)
+
+    def get_repository_cursor(self):
+        return Cursor(self.dji.Repository, self.get_raw_repository_data)
+
+    def get_media_cursor(self):
+        return Cursor(self.dji.Media, self.get_raw_object_data)
+
+    def has_gramps_id(self, obj_key, gramps_id):
+        key2table = {
+            PERSON_KEY:     self.dji.Person, 
+            FAMILY_KEY:     self.dji.Family, 
+            SOURCE_KEY:     self.dji.Source, 
+            CITATION_KEY:   self.dji.Citation, 
+            EVENT_KEY:      self.dji.Event, 
+            MEDIA_KEY:      self.dji.Media, 
+            PLACE_KEY:      self.dji.Place, 
+            REPOSITORY_KEY: self.dji.Repository, 
+            NOTE_KEY:       self.dji.Note, 
+            }
+        table = key2table[obj_key]
+        return table.filter(gramps_id=gramps_id).count() > 0
+
+    def has_person_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Person.filter(handle=handle).count() == 1
+
+    def has_family_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Family.filter(handle=handle).count() == 1
+
+    def has_citation_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Citation.filter(handle=handle).count() == 1
+
+    def has_source_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Source.filter(handle=handle).count() == 1
+
+    def has_repository_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Repository.filter(handle=handle).count() == 1
+
+    def has_note_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Note.filter(handle=handle).count() == 1
+
+    def has_place_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Place.filter(handle=handle).count() == 1
+
+    def has_event_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Event.filter(handle=handle).count() == 1
+
+    def has_tag_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Tag.filter(handle=handle).count() == 1
+
+    def has_object_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Media.filter(handle=handle).count() == 1
+
+    def has_name_group_key(self, key):
+        # FIXME:
+        return False
+
+    def set_name_group_mapping(self, key, value):
+        # FIXME:
+        pass
+
+    def set_default_person_handle(self, handle):
+        pass
+
+    def set_mediapath(self, mediapath):
+        pass
+
+    def get_raw_person_data(self, handle):
+        try:
+            return self.dji.get_person(self.dji.Person.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_family_data(self, handle):
+        try:
+            return self.dji.get_family(self.dji.Family.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_citation_data(self, handle):
+        try:
+            return self.dji.get_citation(self.dji.Citation.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_source_data(self, handle):
+        try:
+            return self.dji.get_source(self.dji.Source.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_repository_data(self, handle):
+        try:
+            return self.dji.get_repository(self.dji.Repository.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_note_data(self, handle):
+        try:
+            return self.dji.get_note(self.dji.Note.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_place_data(self, handle):
+        try:
+            return self.dji.get_place(self.dji.Place.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_object_data(self, handle):
+        try:
+            return self.dji.get_media(self.dji.Media.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_tag_data(self, handle):
+        try:
+            return self.dji.get_tag(self.dji.Tag.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_event_data(self, handle):
+        try:
+            return self.dji.get_event(self.dji.Event.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def add_person(self, person, trans, set_gid=True):
+        if not person.handle:
+            person.handle = create_id()
+        if not person.gramps_id and set_gid:
+            person.gramps_id = self.find_next_person_gramps_id()
+        self.commit_person(person, trans)
+        self.emit("person-add", ([person.handle],))
+        return person.handle
+
+    def add_family(self, family, trans, set_gid=True):
+        if not family.handle:
+            family.handle = create_id()
+        if not family.gramps_id and set_gid:
+            family.gramps_id = self.find_next_family_gramps_id()
+        self.commit_family(family, trans)
+        self.emit("family-add", ([family.handle],))
+        return family.handle
+
+    def add_citation(self, citation, trans, set_gid=True):
+        if not citation.handle:
+            citation.handle = create_id()
+        if not citation.gramps_id and set_gid:
+            citation.gramps_id = self.find_next_citation_gramps_id()
+        self.commit_citation(citation, trans)
+        self.emit("citation-add", ([citation.handle],))
+        return citation.handle
+
+    def add_source(self, source, trans, set_gid=True):
+        if not source.handle:
+            source.handle = create_id()
+        if not source.gramps_id and set_gid:
+            source.gramps_id = self.find_next_source_gramps_id()
+        self.commit_source(source, trans)
+        self.emit("source-add", ([source.handle],))
+        return source.handle
+
+    def add_repository(self, repository, trans, set_gid=True):
+        if not repository.handle:
+            repository.handle = create_id()
+        if not repository.gramps_id and set_gid:
+            repository.gramps_id = self.find_next_repository_gramps_id()
+        self.commit_repository(repository, trans)
+        self.emit("repository-add", ([repository.handle],))
+        return repository.handle
+
+    def add_note(self, note, trans, set_gid=True):
+        if not note.handle:
+            note.handle = create_id()
+        if not note.gramps_id and set_gid:
+            note.gramps_id = self.find_next_note_gramps_id()
+        self.commit_note(note, trans)
+        self.emit("note-add", ([note.handle],))
+        return note.handle
+
+    def add_place(self, place, trans, set_gid=True):
+        if not place.handle:
+            place.handle = create_id()
+        if not place.gramps_id and set_gid:
+            place.gramps_id = self.find_next_place_gramps_id()
+        self.commit_place(place, trans)
+        return place.handle
+
+    def add_event(self, event, trans, set_gid=True):
+        if not event.handle:
+            event.handle = create_id()
+        if not event.gramps_id and set_gid:
+            event.gramps_id = self.find_next_event_gramps_id()
+        self.commit_event(event, trans)
+        return event.handle
+
+    def add_tag(self, tag, trans):
+        if not tag.handle:
+            tag.handle = create_id()
+        self.commit_event(tag, trans)
+        return tag.handle
+
+    def add_object(self, obj, transaction, set_gid=True):
+        """
+        Add a MediaObject to the database, assigning internal IDs if they have
+        not already been defined.
+        
+        If not set_gid, then gramps_id is not set.
+        """
+        if not obj.handle:
+            obj.handle = create_id()
+        if not obj.gramps_id and set_gid:
+            obj.gramps_id = self.find_next_object_gramps_id()
+        self.commit_media_object(obj, transaction)
+        return obj.handle
+
+    def commit_person(self, person, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[person.handle] = person
+        else:
+            raw = person.serialize()
+            items = self.dji.Person.filter(handle=person.handle)
+            if items.count() > 0:
+                # Hack, for the moment: delete and re-add
+                items[0].delete()
+            self.dji.add_person(person.serialize())
+            self.dji.add_person_detail(person.serialize())
+            if items.count() > 0:
+                self.emit("person-update", ([person.handle],))
+            else:
+                self.emit("person-add", ([person.handle],))
+
+    def commit_family(self, family, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[family.handle] = family
+        else:
+            raw = family.serialize()
+            items = self.dji.Family.filter(handle=family.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_family(family.serialize())
+            self.dji.add_family_detail(family.serialize())
+            if items.count() > 0:
+                self.emit("family-update", ([family.handle],))
+            else:
+                self.emit("family-add", ([family.handle],))
+
+    def commit_citation(self, citation, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[citation.handle] = citation
+        else:
+            raw = citation.serialize()
+            items = self.dji.Citation.filter(handle=citation.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_citation(citation.serialize())
+            self.dji.add_citation_detail(citation.serialize())
+            if items.count() > 0:
+                self.emit("citation-update", ([citation.handle],))
+            else:
+                self.emit("citation-add", ([citation.handle],))
+
+    def commit_source(self, source, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[source.handle] = source
+        else:
+            raw = source.serialize()
+            items = self.dji.Source.filter(handle=source.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_source(source.serialize())
+            self.dji.add_source_detail(source.serialize())
+            if items.count() > 0:
+                self.emit("source-update", ([source.handle],))
+            else:
+                self.emit("source-add", ([source.handle],))
+
+    def commit_repository(self, repository, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[repository.handle] = repository
+        else:
+            raw = repository.serialize()
+            items = self.dji.Repository.filter(handle=repository.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_repository(repository.serialize())
+            self.dji.add_repository_detail(repository.serialize())
+            if items.count() > 0:
+                self.emit("repository-update", ([repository.handle],))
+            else:
+                self.emit("repository-add", ([repository.handle],))
+
+    def commit_note(self, note, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[note.handle] = note
+        else:
+            raw = note.serialize()
+            items = self.dji.Note.filter(handle=note.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_note(note.serialize())
+            self.dji.add_note_detail(note.serialize())
+            if items.count() > 0:
+                self.emit("note-update", ([note.handle],))
+            else:
+                self.emit("note-add", ([note.handle],))
+
+    def commit_place(self, place, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[place.handle] = place
+        else:
+            raw = place.serialize()
+            items = self.dji.Place.filter(handle=place.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_place(place.serialize())
+            self.dji.add_place_detail(place.serialize())
+            if items.count() > 0:
+                self.emit("place-update", ([place.handle],))
+            else:
+                self.emit("place-add", ([place.handle],))
+
+    def commit_event(self, event, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[event.handle] = event
+        else:
+            raw = event.serialize()
+            items = self.dji.Event.filter(handle=event.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_event(event.serialize())
+            self.dji.add_event_detail(event.serialize())
+            if items.count() > 0:
+                self.emit("event-update", ([event.handle],))
+            else:
+                self.emit("event-add", ([event.handle],))
+
+    def commit_tag(self, tag, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[tag.handle] = tag
+        else:
+            raw = tag.serialize()
+            items = self.dji.Tag.filter(handle=tag.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_tag(tag.serialize())
+            self.dji.add_tag_detail(tag.serialize())
+            if items.count() > 0:
+                self.emit("tag-update", ([tag.handle],))
+            else:
+                self.emit("tag-add", ([tag.handle],))
+
+    def commit_media_object(self, media, transaction, change_time=None):
+        """
+        Commit the specified MediaObject to the database, storing the changes
+        as part of the transaction.
+        """
+        if self.use_import_cache:
+            self.import_cache[obj.handle] = media
+        else:
+            raw = media.serialize()
+            items = self.dji.Media.filter(handle=media.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_media(media.serialize())
+            self.dji.add_media_detail(media.serialize())
+            if items.count() > 0:
+                self.emit("media-update", ([media.handle],))
+            else:
+                self.emit("media-add", ([media.handle],))
+
+    def get_gramps_ids(self, obj_key):
+        key2table = {
+            PERSON_KEY:     self.id_trans, 
+            FAMILY_KEY:     self.fid_trans, 
+            CITATION_KEY:   self.cid_trans, 
+            SOURCE_KEY:     self.sid_trans, 
+            EVENT_KEY:      self.eid_trans, 
+            MEDIA_KEY:      self.oid_trans, 
+            PLACE_KEY:      self.pid_trans, 
+            REPOSITORY_KEY: self.rid_trans, 
+            NOTE_KEY:       self.nid_trans, 
+            }
+
+        table = key2table[obj_key]
+        return list(table.keys())
+
+    def transaction_begin(self, transaction):
+        return 
+
+    def set_researcher(self, owner):
+        pass
+
+    def copy_from_db(self, db):
+        """
+        A (possibily) implementation-specific method to get data from
+        db into this database.
+        """
+        # First we add the primary objects:
+        for key in db._tables.keys():
+            cursor = db._tables[key]["cursor_func"]
+            for (handle, data) in cursor():
+                if key == "Person":
+                    self.dji.add_person(data)
+                elif key == "Family":
+                    self.dji.add_family(data)
+                elif key == "Event":
+                    self.dji.add_event(data)
+                elif key == "Place":
+                    self.dji.add_place(data)
+                elif key == "Repository":
+                    self.dji.add_repository(data)
+                elif key == "Citation":
+                    self.dji.add_citation(data)
+                elif key == "Source":
+                    self.dji.add_source(data)
+                elif key == "Note":
+                    self.dji.add_note(data)
+                elif key == "Media":
+                    self.dji.add_media(data)
+                elif key == "Tag":
+                    self.dji.add_tag(data)
+        for key in db._tables.keys():
+            cursor = db._tables[key]["cursor_func"]
+            for (handle, data) in cursor():
+                if key == "Person":
+                    self.dji.add_person_detail(data)
+                elif key == "Family":
+                    self.dji.add_family_detail(data)
+                elif key == "Event":
+                    self.dji.add_event_detail(data)
+                elif key == "Place":
+                    self.dji.add_place_detail(data)
+                elif key == "Repository":
+                    self.dji.add_repository_detail(data)
+                elif key == "Citation":
+                    self.dji.add_citation_detail(data)
+                elif key == "Source":
+                    self.dji.add_source_detail(data)
+                elif key == "Note":
+                    self.dji.add_note_detail(data)
+                elif key == "Media":
+                    self.dji.add_media_detail(data)
+                elif key == "Tag":
+                    self.dji.add_tag_detail(data)
+            # Next we add the links:
+        self.dji.update_publics()
+
+    def get_from_name_and_handle(self, table_name, handle):
+        """
+        Returns a gen.lib object (or None) given table_name and
+        handle.
+
+        Examples:
+
+        >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008")
+        >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23")
+        """
+        if table_name in self._tables:
+            return self._tables[table_name]["handle_func"](handle)
+        return None
+
+    def is_empty(self):
+        """
+        Is the database empty?
+        """
+        return (self.get_number_of_people() == 0 and
+                self.get_number_of_events() == 0 and
+                self.get_number_of_places() == 0 and
+                self.get_number_of_tags() == 0 and
+                self.get_number_of_families() == 0 and
+                self.get_number_of_notes() == 0 and
+                self.get_number_of_citations() == 0 and
+                self.get_number_of_sources() == 0 and
+                self.get_number_of_media_objects() == 0 and
+                self.get_number_of_repositories() == 0)
+                
+    __callback_map = {}
+
+    def set_prefixes(self, person, media, family, source, citation,
+                     place, event, repository, note):
+        self.set_person_id_prefix(person)
+        self.set_object_id_prefix(media)
+        self.set_family_id_prefix(family)
+        self.set_source_id_prefix(source)
+        self.set_citation_id_prefix(citation)
+        self.set_place_id_prefix(place)
+        self.set_event_id_prefix(event)
+        self.set_repository_id_prefix(repository)
+        self.set_note_id_prefix(note)
+
+    def has_changed(self):
+        return False
+
+    def find_backlink_handles(self, handle, include_classes=None):
+        ## FIXME: figure out how to get objects that refer
+        ## to this handle
+        return []
+
+    def get_note_bookmarks(self):
+        return self.note_bookmarks
+
+    def get_media_bookmarks(self):
+        return self.media_bookmarks
+
+    def get_repo_bookmarks(self):
+        return self.repo_bookmarks
+
+    def get_citation_bookmarks(self):
+        return self.citation_bookmarks
+
+    def get_source_bookmarks(self):
+        return self.source_bookmarks
+
+    def get_place_bookmarks(self):
+        return self.place_bookmarks
+
+    def get_event_bookmarks(self):
+        return self.event_bookmarks
+
+    def get_bookmarks(self):
+        return self.bookmarks
+
+    def get_family_bookmarks(self):
+        return self.family_bookmarks
+
+    def get_save_path(self):
+        return "/tmp/"
+
+    ## Get types:
+    def get_event_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Event instances
+        in the database.
+        """
+        return list(self.event_attributes)
+
+    def get_event_types(self):
+        """
+        Return a list of all event types in the database.
+        """
+        return list(self.event_names)
+
+    def get_person_event_types(self):
+        """
+        Deprecated:  Use get_event_types
+        """
+        return list(self.event_names)
+
+    def get_person_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Person instances 
+        in the database.
+        """
+        return list(self.individual_attributes)
+
+    def get_family_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Family instances 
+        in the database.
+        """
+        return list(self.family_attributes)
+
+    def get_family_event_types(self):
+        """
+        Deprecated:  Use get_event_types
+        """
+        return list(self.event_names)
+
+    def get_media_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Media and MediaRef 
+        instances in the database.
+        """
+        return list(self.media_attributes)
+
+    def get_family_relation_types(self):
+        """
+        Return a list of all relationship types assocated with Family
+        instances in the database.
+        """
+        return list(self.family_rel_types)
+
+    def get_child_reference_types(self):
+        """
+        Return a list of all child reference types assocated with Family
+        instances in the database.
+        """
+        return list(self.child_ref_types)
+
+    def get_event_roles(self):
+        """
+        Return a list of all custom event role names assocated with Event
+        instances in the database.
+        """
+        return list(self.event_role_names)
+
+    def get_name_types(self):
+        """
+        Return a list of all custom names types assocated with Person
+        instances in the database.
+        """
+        return list(self.name_types)
+
+    def get_origin_types(self):
+        """
+        Return a list of all custom origin types assocated with Person/Surname
+        instances in the database.
+        """
+        return list(self.origin_types)
+
+    def get_repository_types(self):
+        """
+        Return a list of all custom repository types assocated with Repository 
+        instances in the database.
+        """
+        return list(self.repository_types)
+
+    def get_note_types(self):
+        """
+        Return a list of all custom note types assocated with Note instances 
+        in the database.
+        """
+        return list(self.note_types)
+
+    def get_source_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Source/Citation
+        instances in the database.
+        """
+        return list(self.source_attributes)
+
+    def get_source_media_types(self):
+        """
+        Return a list of all custom source media types assocated with Source 
+        instances in the database.
+        """
+        return list(self.source_media_types)
+
+    def get_url_types(self):
+        """
+        Return a list of all custom names types assocated with Url instances 
+        in the database.
+        """
+        return list(self.url_types)
+
+    def get_place_types(self):
+        """
+        Return a list of all custom place types assocated with Place instances
+        in the database.
+        """
+        return list(self.place_types)
+
+    def get_default_handle(self):
+        people = self.dji.Person.all()
+        if people.count() > 0:
+            return people[0].handle
+        return None
+
+    def close(self):
+        pass
+
+    def get_surname_list(self):
+        return []
+
+    def is_open(self):
+        return True
+
+    def get_table_names(self):
+        """Return a list of valid table names."""
+        return list(self._tables.keys())
+
+    def find_initial_person(self):
+        return self.get_default_person()
+
+    # Removals:
+    def remove_person(self, handle, txn):
+        self.dji.Person.filter(handle=handle)[0].delete()
+        self.emit("person-delete", ([handle],))
+
+    def remove_source(self, handle, transaction):
+        self.dji.Source.filter(handle=handle)[0].delete()
+        self.emit("source-delete", ([handle],))
+
+    def remove_citation(self, handle, transaction):
+        self.dji.Citation.filter(handle=handle)[0].delete()
+        self.emit("citation-delete", ([handle],))
+
+    def remove_event(self, handle, transaction):
+        self.dji.Event.filter(handle=handle)[0].delete()
+        self.emit("event-delete", ([handle],))
+
+    def remove_object(self, handle, transaction):
+        self.dji.Media.filter(handle=handle)[0].delete()
+        self.emit("media-delete", ([handle],))
+
+    def remove_place(self, handle, transaction):
+        self.dji.Place.filter(handle=handle)[0].delete()
+        self.emit("place-delete", ([handle],))
+
+    def remove_family(self, handle, transaction):
+        self.dji.Family.filter(handle=handle)[0].delete()
+        self.emit("family-delete", ([handle],))
+
+    def remove_repository(self, handle, transaction):
+        self.dji.Repository.filter(handle=handle)[0].delete()
+        self.emit("repository-delete", ([handle],))
+
+    def remove_note(self, handle, transaction):
+        self.dji.Note.filter(handle=handle)[0].delete()
+        self.emit("note-delete", ([handle],))
+
+    def remove_tag(self, handle, transaction):
+        self.dji.Tag.filter(handle=handle)[0].delete()
+        self.emit("tag-delete", ([handle],))
+
+    def remove_from_surname_list(self, person):
+        ## FIXME
+        pass
+    
+    def get_dbname(self):
+        return "Django Database"
+
+    ## missing
+
+    def find_place_child_handles(self, handle):
+        pass
+
+    def get_cursor(self, table, txn=None, update=False, commit=False):
+        pass
+
+    def get_from_name_and_handle(self, table_name, handle):
+        """
+        Returns a gen.lib object (or None) given table_name and
+        handle.
+
+        Examples:
+
+        >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008")
+        >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23")
+        """
+        if table_name in self._tables:
+            return self._tables[table_name]["handle_func"](handle)
+        return None
+
+    def get_number_of_records(self, table):
+        pass
+
+    def get_place_parent_cursor(self):
+        pass
+
+    def get_place_tree_cursor(self):
+        pass
+
+    def get_table_metadata(self, table_name):
+        """Return the metadata for a valid table name."""
+        if table_name in self._tables:
+            return self._tables[table_name]
+        return None
+
+    def get_transaction_class(self):
+        pass
+
+    def undo(self, update_history=True):
+        # FIXME:
+        return self.undodb.undo(update_history)
+
+    def redo(self, update_history=True):
+        # FIXME:
+        return self.undodb.redo(update_history)
+
+    def backup(self):
+        pass
+
+    def restore(self):
+        pass
diff --git a/gramps/plugins/database/dictionarydb.gpr.py b/gramps/plugins/database/dictionarydb.gpr.py
new file mode 100644
index 000000000..3b0e62eca
--- /dev/null
+++ b/gramps/plugins/database/dictionarydb.gpr.py
@@ -0,0 +1,31 @@
+#
+# Gramps - a GTK+/GNOME based genealogy program
+#
+# Copyright (C) 2015 Douglas Blank <doug.blank@gmail.com>
+#
+# 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+plg = newplugin()
+plg.id    = 'dictionarydb'
+plg.name  = _("Dictionary Database Backend")
+plg.name_accell  = _("Di_ctionary Database Backend")
+plg.description =  _("Dictionary (in-memory) Database Backend")
+plg.version = '1.0'
+plg.gramps_target_version = "4.2"
+plg.status = STABLE
+plg.fname = 'dictionarydb.py'
+plg.ptype = DATABASE
+plg.databaseclass = 'DictionaryDb'
diff --git a/gramps/plugins/database/dictionary.py b/gramps/plugins/database/dictionarydb.py
similarity index 98%
rename from gramps/plugins/database/dictionary.py
rename to gramps/plugins/database/dictionarydb.py
index 2d785ef6e..a3dbd9586 100644
--- a/gramps/plugins/database/dictionary.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -28,17 +28,17 @@ import pickle
 import base64
 import time
 import re
-from . import DbReadBase, DbWriteBase, DbTxn
-from . import (PERSON_KEY,
-               FAMILY_KEY,
-               CITATION_KEY,
-               SOURCE_KEY,
-               EVENT_KEY,
-               MEDIA_KEY,
-               PLACE_KEY,
-               REPOSITORY_KEY,
-               NOTE_KEY,
-               TAG_KEY)
+from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn
+from gramps.gen.db import (PERSON_KEY,
+                           FAMILY_KEY,
+                           CITATION_KEY,
+                           SOURCE_KEY,
+                           EVENT_KEY,
+                           MEDIA_KEY,
+                           PLACE_KEY,
+                           REPOSITORY_KEY,
+                           NOTE_KEY,
+                           TAG_KEY)
 
 from gramps.gen.utils.id import create_id
 from gramps.gen.lib.researcher import Researcher
diff --git a/gramps/plugins/database/django_support/libdjango.py b/gramps/plugins/database/django_support/libdjango.py
new file mode 100644
index 000000000..fa0a596c8
--- /dev/null
+++ b/gramps/plugins/database/django_support/libdjango.py
@@ -0,0 +1,2063 @@
+# Gramps - a GTK+/GNOME based genealogy program
+#
+# Copyright (C) 2009         Douglas S. Blank <doug.blank@gmail.com>
+#
+# 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+""" Interface to Django models """
+
+#------------------------------------------------------------------------
+#
+# Python Modules
+#
+#------------------------------------------------------------------------
+import time
+import sys
+import pickle
+import base64
+import collections
+
+#------------------------------------------------------------------------
+#
+# Django Modules
+#
+#------------------------------------------------------------------------
+from django.contrib.contenttypes.models import ContentType
+from django.db import transaction
+
+#------------------------------------------------------------------------
+#
+# Gramps Modules
+#
+#------------------------------------------------------------------------
+import gramps.webapp.grampsdb.models as models
+from gramps.gen.lib import Name
+from gramps.gen.utils.id import create_id
+from gramps.gen.constfunc import conv_to_unicode
+
+# To get a django person from a django database:
+#    djperson = dji.Person.get(handle='djhgsdh324hjg234hj24')
+# 
+# To turn the djperson into a Gramps Person:
+#    tuple = dji.get_person(djperson)
+#    gperson = lib.gen.Person(tuple)
+# OR
+#    gperson = dbdjango.DbDjango().get_person_from_handle(handle)
+
+def check_diff(item, raw):
+    encoded = str(base64.encodebytes(pickle.dumps(raw)), "utf-8")
+    if item.cache != encoded:
+        print("Different:", item.__class__.__name__, item.gramps_id)
+        print("raw  :", raw)
+        print("cache:", item.from_cache())
+        # FIXING, TOO:
+        item.save_cache()
+
+#-------------------------------------------------------------------------
+#
+# Import functions
+#
+#-------------------------------------------------------------------------
+def lookup_role_index(role0, event_ref_list):
+    """
+    Find the handle in a unserialized event_ref_list and return code.
+    """
+    if role0 is None:
+        return -1
+    else:
+        count = 0
+        for event_ref in event_ref_list:
+            (private, note_list, attribute_list, ref, erole) = event_ref
+            try:
+                event = models.Event.objects.get(handle=ref)
+            except:
+                return -1
+            if event.event_type[0] == role0:
+                return count
+            count += 1
+        return -1
+
+def totime(dtime):
+    if dtime:
+        return int(time.mktime(dtime.timetuple()))
+    else:
+        return 0
+
+#-------------------------------------------------------------------------
+#
+# Export functions
+#
+#-------------------------------------------------------------------------
+def todate(t):
+    return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(t))
+
+def lookup(index, event_ref_list):
+    """
+    Get the unserialized event_ref in an list of them and return it.
+    """
+    if index < 0:
+        return None
+    else:
+        count = 0
+        for event_ref in event_ref_list:
+            (private, note_list, attribute_list, ref, role) = event_ref
+            if index == count:
+                return ref
+            count += 1
+        return None
+
+def get_datamap(grampsclass):
+    return [x[0] for x in grampsclass._DATAMAP if x[0] != grampsclass.CUSTOM]
+
+#-------------------------------------------------------------------------
+#
+# Django Interface
+#
+#-------------------------------------------------------------------------
+class DjangoInterface(object):
+    """
+    DjangoInterface for interoperating between Gramps and Django.
+    
+    This interface comes in a number of parts: 
+        get_ITEMS()
+        add_ITEMS()
+
+    get_ITEM(ITEM)
+
+    Given an ITEM from a Django table, construct a Gramps Raw Data tuple.
+
+    add_ITEM(data)
+
+    Given a Gramps Raw Data tuple, add the data to the Django tables.
+    
+
+    """
+    def __init__(self):
+        self.debug = 0
+
+    def __getattr__(self, name):
+        """
+        Django Objects database interface.
+
+        >>> self.Person.all()
+        >>> self.Person.get(id=1)
+        >>> self.Person.get(handle='gh71234dhf3746347734')
+        """
+        if hasattr(models, name):
+            return getattr(models, name).objects
+        else:
+            raise AttributeError("no such model: '%s'" % name)
+
+    def get_next_id(self, obj, prefix):
+        """
+        Get next gramps_id
+
+        >>> dji.get_next_id(Person, "P")
+        'P0002'
+        >>> dji.get_next_id(Media, "M")
+        'M2349'
+        """
+        ids = [o["gramps_id"] for o in obj.objects.values("gramps_id")]
+        count = 1
+        while "%s%04d" % (prefix, count) in ids: 
+            count += 1
+        return "%s%04d" % (prefix, count)
+
+    def get_model(self, name):
+        if hasattr(models, name):
+            return getattr(models, name)
+        elif hasattr(models, name.title()):
+            return getattr(models, name.title())
+        else:
+            raise AttributeError("no such model: '%s'" % name)
+
+    # -----------------------------------------------
+    # Get methods to retrieve list data from the tables
+    # -----------------------------------------------
+
+    def clear_tables(self, *args):
+        return models.clear_tables(*args)
+
+    def get_tag_list(self, obj):
+        return obj.get_tag_list()
+
+    def get_attribute_list(self, obj):
+        obj_type = ContentType.objects.get_for_model(obj)
+        attribute_list = models.Attribute.objects.filter(object_id=obj.id, 
+                                                     object_type=obj_type)
+        return list(map(self.pack_attribute, attribute_list))
+
+    def get_primary_name(self, person):
+        names = person.name_set.filter(preferred=True).order_by("order")
+        if len(names) > 0:
+            return Name.create(self.pack_name(names[0]))
+        else:
+            return Name()
+      
+    def get_alternate_names(self, person):
+        names = person.name_set.filter(preferred=False).order_by("order")
+        return [Name.create(self.pack_name(n)) for n in names]
+
+    def get_names(self, person, preferred):
+        names = person.name_set.filter(preferred=preferred).order_by("order")
+        if preferred:
+            if len(names) > 0:
+                return self.pack_name(names[0])
+            else:
+                return Name().serialize()
+        else:
+            return list(map(self.pack_name, names))
+     
+    def get_source_attribute_list(self, source): 
+        return [(map.private, map.key, map.value) for map in source.sourceattribute_set.all().order_by("order")]
+
+    def get_citation_attribute_list(self, citation): 
+        return [(map.private, map.key, map.value) for map in citation.citationattribute_set.all().order_by("order")]
+
+    def get_media_list(self, obj):
+        obj_type = ContentType.objects.get_for_model(obj)
+        mediarefs = models.MediaRef.objects.filter(object_id=obj.id, 
+                                               object_type=obj_type)
+        return list(map(self.pack_media_ref, mediarefs))
+
+    def get_note_list(self, obj):
+        obj_type = ContentType.objects.get_for_model(obj)
+        noterefs = models.NoteRef.objects.filter(object_id=obj.id, 
+                                             object_type=obj_type)
+        return [noteref.ref_object.handle for noteref in noterefs]
+
+    def get_repository_ref_list(self, obj):
+        obj_type = ContentType.objects.get_for_model(obj)
+        reporefs = models.RepositoryRef.objects.filter(object_id=obj.id, 
+                                                   object_type=obj_type)
+        return list(map(self.pack_repository_ref, reporefs))
+
+    def get_place_ref_list(self, obj):
+        obj_type = ContentType.objects.get_for_model(obj)
+        refs = models.PlaceRef.objects.filter(object_id=obj.id, 
+                                              object_type=obj_type)
+        return list(map(self.pack_place_ref, refs))
+
+    def get_url_list(self, obj):
+        return list(map(self.pack_url, obj.url_set.all().order_by("order")))
+
+    def get_address_list(self, obj, with_parish): # person or repository
+        addresses = obj.address_set.all().order_by("order")
+        return [self.pack_address(address, with_parish)
+                    for address in addresses]
+
+    def get_child_ref_list(self, family):
+        obj_type = ContentType.objects.get_for_model(family)
+        childrefs = models.ChildRef.objects.filter(object_id=family.id,
+                                        object_type=obj_type).order_by("order")
+        return list(map(self.pack_child_ref, childrefs))
+
+    def get_citation_list(self, obj):
+        obj_type = ContentType.objects.get_for_model(obj)
+        citationrefs = models.CitationRef.objects.filter(object_id=obj.id,
+                                                         object_type=obj_type).order_by("order")
+        return [citationref.citation.handle for citationref in citationrefs]
+
+    def get_event_refs(self, obj, order="order"):
+        obj_type = ContentType.objects.get_for_model(obj)
+        eventrefs = models.EventRef.objects.filter(object_id=obj.id,
+                                        object_type=obj_type).order_by(order)
+        return eventrefs
+
+    def get_event_ref_list(self, obj):
+        obj_type = ContentType.objects.get_for_model(obj)
+        eventrefs = models.EventRef.objects.filter(object_id=obj.id,
+                                        object_type=obj_type).order_by("order")
+        return list(map(self.pack_event_ref, eventrefs))
+
+    def get_family_list(self, person): # person has families
+        return [fam.family.handle for fam in 
+                models.MyFamilies.objects.filter(person=person).order_by("order")]
+    
+    def get_parent_family_list(self, person): # person's parents has families
+        return [fam.family.handle for fam in 
+                models.MyParentFamilies.objects.filter(person=person).order_by("order")]
+
+    def get_person_ref_list(self, person):
+        obj_type = ContentType.objects.get_for_model(person)
+        return list(map(self.pack_person_ref, 
+                models.PersonRef.objects.filter(object_id=person.id, 
+                                            object_type=obj_type)))
+
+    def get_lds_list(self, obj): # person or family
+        return list(map(self.pack_lds, obj.lds_set.all().order_by("order")))
+
+    def get_place_handle(self, obj): # obj is event
+        if obj.place:
+            return obj.place.handle
+        return ''
+
+    ## Packers:
+
+    def get_event(self, event):
+        handle = event.handle
+        gid = event.gramps_id
+        the_type = tuple(event.event_type)
+        description = event.description
+        change = totime(event.last_changed)
+        private = event.private
+        note_list = self.get_note_list(event)           
+        citation_list = self.get_citation_list(event)   
+        media_list = self.get_media_list(event)         
+        attribute_list = self.get_attribute_list(event)
+        date = self.get_date(event)
+        place_handle = self.get_place_handle(event)
+        tag_list = self.get_tag_list(event)
+        return (str(handle), gid, the_type, date, description, place_handle, 
+                citation_list, note_list, media_list, attribute_list,
+                change, tag_list, private)
+
+    def get_note_markup(self, note):
+        retval = []
+        markups = models.Markup.objects.filter(note=note).order_by("order")
+        for markup in markups:
+            if markup.string and markup.string.isdigit():
+                value = int(markup.string)
+            else:
+                value = markup.string 
+            start_stop_list  = markup.start_stop_list
+            ss_list = eval(start_stop_list)
+            retval += [(tuple(markup.styled_text_tag_type), value, ss_list)]
+        return retval
+
+    def get_tag(self, tag):
+        changed = totime(tag.last_changed)
+        return (str(tag.handle),
+                tag.name,
+                tag.color,
+                tag.priority,
+                changed)
+
+    def get_note(self, note):
+        styled_text = [note.text, self.get_note_markup(note)]
+        changed = totime(note.last_changed)
+        tag_list = self.get_tag_list(note)
+        return (str(note.handle), 
+                note.gramps_id, 
+                styled_text, 
+                note.preformatted, 
+                tuple(note.note_type), 
+                changed, 
+                tag_list, 
+                note.private)
+
+    def get_family(self, family):
+        child_ref_list = self.get_child_ref_list(family)
+        event_ref_list = self.get_event_ref_list(family)
+        media_list = self.get_media_list(family)
+        attribute_list = self.get_attribute_list(family)
+        lds_seal_list = self.get_lds_list(family)
+        citation_list = self.get_citation_list(family)
+        note_list = self.get_note_list(family)
+        tag_list = self.get_tag_list(family)
+        if family.father:
+            father_handle = family.father.handle
+        else:
+            father_handle = ''
+        if family.mother:
+            mother_handle = family.mother.handle
+        else:
+            mother_handle = ''
+        return (str(family.handle), family.gramps_id, 
+                father_handle, mother_handle,
+                child_ref_list, tuple(family.family_rel_type), 
+                event_ref_list, media_list,
+                attribute_list, lds_seal_list, 
+                citation_list, note_list,
+                totime(family.last_changed), 
+                tag_list, 
+                family.private)
+
+    def get_repository(self, repository):
+        note_list = self.get_note_list(repository)
+        address_list = self.get_address_list(repository, with_parish=False)
+        url_list = self.get_url_list(repository)
+        tag_list = self.get_tag_list(repository)
+        return (str(repository.handle), 
+                repository.gramps_id, 
+                tuple(repository.repository_type),
+                repository.name, 
+                note_list,
+                address_list, 
+                url_list, 
+                totime(repository.last_changed), 
+                tag_list,
+                repository.private)
+
+    def get_citation(self, citation):
+        note_list = self.get_note_list(citation)
+        media_list = self.get_media_list(citation)
+        attribute_list = self.get_citation_attribute_list(citation)
+        tag_list = self.get_tag_list(citation)
+        date = self.get_date(citation)
+        # I guess citations can have no source
+        if citation.source:
+            handle = citation.source.handle
+        else:
+            handle = None
+        return (str(citation.handle),  
+                citation.gramps_id, 
+                date,
+                citation.page, 
+                citation.confidence, 
+                handle,
+                note_list, 
+                media_list, 
+                attribute_list, 
+                totime(citation.last_changed), 
+                tag_list,
+                citation.private) 
+
+    def get_source(self, source):
+        note_list = self.get_note_list(source)
+        media_list = self.get_media_list(source)
+        attribute_list = self.get_source_attribute_list(source)
+        reporef_list = self.get_repository_ref_list(source)
+        tag_list = self.get_tag_list(source)
+        return (str(source.handle), 
+                source.gramps_id, 
+                source.title,
+                source.author, 
+                source.pubinfo,
+                note_list,
+                media_list,
+                source.abbrev,
+                totime(source.last_changed), 
+                attribute_list,
+                reporef_list,
+                tag_list,
+                source.private)
+
+    def get_media(self, media):
+        attribute_list = self.get_attribute_list(media)
+        citation_list = self.get_citation_list(media)
+        note_list = self.get_note_list(media)
+        tag_list = self.get_tag_list(media)
+        date = self.get_date(media)
+        return (str(media.handle), 
+                media.gramps_id, 
+                conv_to_unicode(media.path, None),
+                str(media.mime), 
+                str(media.desc),
+                media.checksum,
+                attribute_list,
+                citation_list,
+                note_list,
+                totime(media.last_changed),
+                date,
+                tag_list,
+                media.private)
+
+    def get_person(self, person):
+        primary_name = self.get_names(person, True) # one
+        alternate_names = self.get_names(person, False) # list
+        event_ref_list = self.get_event_ref_list(person)
+        family_list = self.get_family_list(person)
+        parent_family_list = self.get_parent_family_list(person)
+        media_list = self.get_media_list(person)
+        address_list = self.get_address_list(person, with_parish=False)
+        attribute_list = self.get_attribute_list(person)
+        url_list = self.get_url_list(person)
+        lds_ord_list = self.get_lds_list(person)
+        pcitation_list = self.get_citation_list(person)
+        pnote_list = self.get_note_list(person)
+        person_ref_list = self.get_person_ref_list(person)
+        # This looks up the events for the first EventType given:
+        death_ref_index = person.death_ref_index
+        birth_ref_index = person.birth_ref_index
+        tag_list = self.get_tag_list(person)
+
+        return (str(person.handle),
+                person.gramps_id,  
+                tuple(person.gender_type)[0],
+                primary_name,       
+                alternate_names,    
+                death_ref_index,    
+                birth_ref_index,    
+                event_ref_list,     
+                family_list,        
+                parent_family_list, 
+                media_list,         
+                address_list,       
+                attribute_list,     
+                url_list,               
+                lds_ord_list,       
+                pcitation_list,       
+                pnote_list,         
+                totime(person.last_changed),             
+                tag_list, 
+                person.private,            
+                person_ref_list)
+
+    def get_date(self, obj):
+        if ((obj.calendar == obj.modifier == obj.quality == obj.sortval == obj.newyear == 0) and
+            obj.text == "" and (not obj.slash1) and (not obj.slash2) and 
+            (obj.day1 == obj.month1 == obj.year1 == 0) and 
+            (obj.day2 == obj.month2 == obj.year2 == 0)):
+            return None
+        elif ((not obj.slash1) and (not obj.slash2) and 
+            (obj.day2 == obj.month2 == obj.year2 == 0)):
+            dateval = (obj.day1, obj.month1, obj.year1, obj.slash1)
+        else:
+            dateval = (obj.day1, obj.month1, obj.year1, obj.slash1, 
+                       obj.day2, obj.month2, obj.year2, obj.slash2)
+        return (obj.calendar, obj.modifier, obj.quality, dateval, 
+                obj.text, obj.sortval, obj.newyear)
+
+    def get_place(self, place):
+        locations = place.location_set.all().order_by("order")
+        alt_location_list = [self.pack_location(location, True) for location in locations]
+        url_list = self.get_url_list(place)
+        media_list = self.get_media_list(place)
+        citation_list = self.get_citation_list(place)
+        note_list = self.get_note_list(place)
+        tag_list = self.get_tag_list(place)
+        place_ref_list = self.get_place_ref_list(place)
+        return (str(place.handle), 
+                place.gramps_id,
+                place.title, 
+                place.long, 
+                place.lat,
+                place_ref_list,
+                place.name,
+                [], ## FIXME: get_alt_names
+                tuple(place.place_type),
+                place.code,
+                alt_location_list,
+                url_list,
+                media_list,
+                citation_list,
+                note_list,
+                totime(place.last_changed), 
+                tag_list,
+                place.private)
+
+    # ---------------------------------
+    # Packers
+    # ---------------------------------
+
+    ## The packers build GRAMPS raw unserialized data.
+
+    ## Reference packers
+
+    def pack_child_ref(self, child_ref):
+        citation_list = self.get_citation_list(child_ref)
+        note_list = self.get_note_list(child_ref) 
+        return (child_ref.private, citation_list, note_list, child_ref.ref_object.handle, 
+                tuple(child_ref.father_rel_type), tuple(child_ref.mother_rel_type))
+
+    def pack_person_ref(self, personref):
+        citation_list = self.get_citation_list(personref)
+        note_list = self.get_note_list(personref)
+        return (personref.private, 
+                citation_list,
+                note_list,
+                personref.ref_object.handle,
+                personref.description)
+
+    def pack_media_ref(self, media_ref):
+        citation_list = self.get_citation_list(media_ref)
+        note_list = self.get_note_list(media_ref)
+        attribute_list = self.get_attribute_list(media_ref)
+        if ((media_ref.x1 == media_ref.y1 == media_ref.x2 == media_ref.y2 == -1) or
+            (media_ref.x1 == media_ref.y1 == media_ref.x2 == media_ref.y2 == 0)):
+            role = None
+        else:
+            role = (media_ref.x1, media_ref.y1, media_ref.x2, media_ref.y2)
+        return (media_ref.private, citation_list, note_list, attribute_list, 
+                media_ref.ref_object.handle, role)
+
+    def pack_repository_ref(self, repo_ref):
+        note_list = self.get_note_list(repo_ref)
+        return (note_list, 
+                repo_ref.ref_object.handle,
+                repo_ref.call_number, 
+                tuple(repo_ref.source_media_type),
+                repo_ref.private)
+
+    def pack_place_ref(self, place_ref):
+        date = self.get_date(place_ref)
+        return (place_ref.ref_object.handle, date)
+
+    def pack_media_ref(self, media_ref):
+        note_list = self.get_note_list(media_ref)
+        attribute_list = self.get_attribute_list(media_ref)
+        citation_list = self.get_citation_list(media_ref)
+        return (media_ref.private, citation_list, note_list, attribute_list, 
+                media_ref.ref_object.handle, (media_ref.x1,
+                                              media_ref.y1,
+                                              media_ref.x2,
+                                              media_ref.y2))
+    
+    def pack_event_ref(self, event_ref):
+        note_list = self.get_note_list(event_ref)
+        attribute_list = self.get_attribute_list(event_ref)
+        return (event_ref.private, note_list, attribute_list, 
+                event_ref.ref_object.handle, tuple(event_ref.role_type))
+
+    def pack_citation(self, citation):
+        handle = citation.handle
+        gid = citation.gramps_id
+        date = self.get_date(citation)
+        page = citation.page
+        confidence = citation.confidence
+        source_handle = citation.source.handle
+        note_list = self.get_note_list(citation)
+        media_list = self.get_media_list(citation)
+        attribute_list = self.get_citation_attribute_list(citation)
+        changed = totime(citation.last_changed)
+        private = citation.private
+        tag_list = self.get_tag_list(citation)
+        return (handle, gid, date, page, confidence, source_handle,
+                note_list, media_list, attribute_list, changed, tag_list, 
+                private) 
+
+    def pack_address(self, address, with_parish):
+        citation_list = self.get_citation_list(address)
+        date = self.get_date(address)
+        note_list = self.get_note_list(address)
+        locations = address.location_set.all().order_by("order")
+        if len(locations) > 0:
+            location = self.pack_location(locations[0], with_parish)
+        else:
+            if with_parish:
+                location = (("", "", "", "", "", "", ""), "")
+            else:
+                location = ("", "", "", "", "", "", "")
+        return (address.private, citation_list, note_list, date, location)
+
+    def pack_lds(self, lds):
+        citation_list = self.get_citation_list(lds)
+        note_list = self.get_note_list(lds)
+        date = self.get_date(lds)
+        if lds.famc:
+            famc = lds.famc.handle
+        else:
+            famc = None
+        place_handle = self.get_place_handle(lds)
+        return (citation_list, note_list, date, lds.lds_type[0], place_handle,
+                famc, lds.temple, lds.status[0], lds.private)
+
+    def pack_source(self, source):
+        note_list = self.get_note_list(source)
+        media_list = self.get_media_list(source)
+        reporef_list = self.get_repository_ref_list(source)
+        attribute_list = self.get_source_attribute_list(source)
+        tag_list = self.get_tag_list(source)
+        return (source.handle, source.gramps_id, source.title,
+                source.author, source.pubinfo,
+                note_list,
+                media_list,
+                source.abbrev,
+                totime(last_changed), attribute_list,
+                reporef_list,
+                tag_list,
+                source.private)
+
+    def pack_name(self, name):
+        citation_list = self.get_citation_list(name)
+        note_list = self.get_note_list(name)
+        date = self.get_date(name)
+        return (name.private, citation_list, note_list, date,
+                name.first_name, name.make_surname_list(), name.suffix,
+                name.title, tuple(name.name_type), 
+                name.group_as, name.sort_as.val, 
+                name.display_as.val, name.call, name.nick, 
+                name.famnick)
+
+    def pack_location(self, loc, with_parish):
+        if with_parish:
+            return ((loc.street, loc.locality, loc.city, loc.county, loc.state, loc.country, 
+                     loc.postal, loc.phone), loc.parish)
+        else:
+            return (loc.street, loc.locality, loc.city, loc.county, loc.state, loc.country, 
+                    loc.postal, loc.phone)
+
+    def pack_url(self, url):
+        return  (url.private, url.path, url.desc, tuple(url.url_type))
+
+    def pack_attribute(self, attribute):
+        citation_list = self.get_citation_list(attribute)
+        note_list = self.get_note_list(attribute)
+        return (attribute.private, 
+                citation_list, 
+                note_list, 
+                tuple(attribute.attribute_type), 
+                attribute.value)
+
+
+    ## Export lists:
+    
+    def add_child_ref_list(self, obj, ref_list):
+        ## Currently, only Family references children
+        for child_data in ref_list:
+            self.add_child_ref(obj, child_data)
+    
+    def add_citation_list(self, obj, citation_list):
+        for citation_handle in citation_list:
+            self.add_citation_ref(obj, citation_handle)
+    
+    def add_event_ref_list(self, obj, event_ref_list):
+        for event_ref in event_ref_list:
+            self.add_event_ref(obj, event_ref)
+    
+    def add_surname_list(self, name, surname_list):
+        order = 1
+        for data in surname_list:
+            (surname_text, prefix, primary, origin_type,
+             connector) = data
+            surname = models.Surname()
+            surname.surname = surname_text
+            surname.prefix = prefix
+            surname.primary = primary
+            surname.name_origin_type = models.get_type(models.NameOriginType, 
+                                                       origin_type)
+            surname.connector = connector
+            surname.name = name
+            surname.order = order
+            surname.save() 
+            order += 1
+
+    def add_note_list(self, obj, note_list):
+        for handle in note_list:
+            # Just the handle
+            try:
+                note = models.Note.objects.get(handle=handle)
+                self.add_note_ref(obj, note)
+            except:
+                print(("ERROR: Note does not exist: '%s'" % 
+                                      str(handle)), file=sys.stderr)
+    
+    def add_alternate_name_list(self, person, alternate_names):
+        for name in alternate_names:
+            if name:
+                self.add_name(person, name, False)
+    
+    def add_parent_family_list(self, person, parent_family_list):
+        for parent_family_data in parent_family_list:
+            self.add_parent_family(person, parent_family_data)
+    
+    def add_media_ref_list(self, person, media_list):
+        for media_data in media_list:
+            self.add_media_ref(person, media_data)
+    
+    def add_attribute_list(self, obj, attribute_list):
+        for attribute_data in attribute_list:
+            self.add_attribute(obj, attribute_data)
+    
+    def add_tag_list(self, obj, tag_list):
+        for tag_handle in tag_list:
+            try:
+                tag = models.Tag.objects.get(handle=tag_handle)
+            except:
+                print(("ERROR: Tag does not exist: '%s'" % 
+                                      str(tag_handle)), file=sys.stderr)
+            obj.tags.add(tag)
+    
+    def add_url_list(self, field, obj, url_list):
+        if not url_list: return None
+        count = 1
+        for url_data in url_list:
+            self.add_url(field, obj, url_data, count) 
+            count += 1
+            
+    def add_person_ref_list(self, obj, person_ref_list):
+        for person_ref_data in person_ref_list:
+            self.add_person_ref(obj, person_ref_data)
+    
+    def add_address_list(self, field, obj, address_list):
+        count = 1
+        for address_data in address_list:
+            self.add_address(field, obj, address_data, count)
+            count += 1
+    
+    def add_lds_list(self, field, obj, lds_ord_list):
+        count = 1
+        for ldsord in lds_ord_list:
+            lds = self.add_lds(field, obj, ldsord, count)
+            #obj.lds_list.add(lds)
+            #obj.save()
+            count += 1
+    
+    def add_repository_ref_list(self, obj, reporef_list):
+        for data in reporef_list:
+            self.add_repository_ref(obj, data)
+    
+    def add_place_ref_list(self, obj, placeref_list):
+        for data in placeref_list:
+            self.add_place_ref(obj, data)
+    
+    def add_family_ref_list(self, person, family_list):
+        for family_handle in family_list:
+            self.add_family_ref(person, family_handle) 
+    
+    def add_alt_name_list(self, place, alt_name_list):
+        print("FIXME: add alt_name_list!", alt_name_list)
+
+    ## Export reference objects:
+
+    def add_person_ref_default(self, obj, person, private=False, desc=None):
+        count = person.references.count()
+        person_ref = models.PersonRef(referenced_by=obj,
+                                  ref_object=person,
+                                  private=private,
+                                  order=count + 1,
+                                  description=desc)
+        person_ref.save()
+
+    def add_person_ref(self, obj, person_ref_data):
+        (private, 
+         citation_list,
+         note_list,
+         handle,
+         desc) = person_ref_data
+        try:
+            person = models.Person.objects.get(handle=handle)
+        except:
+            print(("ERROR: Person does not exist: '%s'" % 
+                                  str(handle)), file=sys.stderr)
+            return
+            
+        count = person.references.count()
+        person_ref = models.PersonRef(referenced_by=obj,
+                                  ref_object=person,
+                                  private=private,
+                                  order=count + 1,
+                                  description=desc)
+        person_ref.save()
+        self.add_note_list(person_ref, note_list)
+        self.add_citation_list(person_ref, citation_list)
+    
+    def add_note_ref(self, obj, note):
+        count = note.references.count()
+        note_ref = models.NoteRef(referenced_by=obj, 
+                              ref_object=note,
+                              private=False,
+                              order=count + 1)
+        note_ref.save()
+    
+    def add_media_ref_default(self, obj, media, private=False, role=None):
+        count = media.references.count()
+        if not role:
+            role = (0,0,0,0)
+        media_ref = models.MediaRef(referenced_by=obj, 
+                                ref_object=media,
+                                x1=role[0],
+                                y1=role[1],
+                                x2=role[2],
+                                y2=role[3],
+                                private=private,
+                                order=count + 1)
+        media_ref.save()
+
+    def add_media_ref(self, obj, media_ref_data):
+        (private, citation_list, note_list, attribute_list, 
+         ref, role) = media_ref_data
+        try:
+            media = models.Media.objects.get(handle=ref)
+        except:
+            print(("ERROR: Media does not exist: '%s'" % 
+                                  str(ref)), file=sys.stderr)
+            return
+        count = media.references.count()
+        if not role:
+            role = (0,0,0,0)
+        media_ref = models.MediaRef(referenced_by=obj, 
+                                ref_object=media,
+                                x1=role[0],
+                                y1=role[1],
+                                x2=role[2],
+                                y2=role[3],
+                                private=private,
+                                order=count + 1)
+        media_ref.save()
+        self.add_note_list(media_ref, note_list)
+        self.add_attribute_list(media_ref, attribute_list)
+        self.add_citation_list(media_ref, citation_list)
+    
+    def add_citation_ref_default(self, obj, citation, private=False):
+        object_type = ContentType.objects.get_for_model(obj)
+        count = models.CitationRef.objects.filter(object_id=obj.id,object_type=object_type).count()
+        citation_ref = models.CitationRef(private=private,
+                                          referenced_by=obj,
+                                          citation=citation,
+                                          order=count + 1)
+        citation_ref.save()
+
+    def add_citation_ref(self, obj, handle):
+        try:
+            citation = models.Citation.objects.get(handle=handle)
+        except:
+            print(("ERROR: Citation does not exist: '%s'" % 
+                                  str(handle)), file=sys.stderr)
+            return
+
+        object_type = ContentType.objects.get_for_model(obj)
+        count = models.CitationRef.objects.filter(object_id=obj.id,object_type=object_type).count()
+        citation_ref = models.CitationRef(private=False,
+                                          referenced_by=obj,
+                                          citation=citation,
+                                          order=count + 1)
+        citation_ref.save()
+
+    def add_citation(self, citation_data):
+        (handle, gid, date, page, confidence, source_handle, note_list,
+         media_list, attribute_list, changed, tag_list, private) = citation_data
+        citation = models.Citation(
+            handle=handle,
+            gramps_id=gid,
+            private=private, 
+            last_changed=todate(changed),
+            confidence=confidence, 
+            page=page)
+        citation.save(save_cache=False)
+
+    def add_citation_detail(self, citation_data):
+        (handle, gid, date, page, confidence, source_handle, note_list,
+         media_list, attribute_list, change, tag_list, private) = citation_data
+        try:
+            citation = models.Citation.objects.get(handle=handle)
+        except:
+            print(("ERROR: Citation does not exist: '%s'" % 
+                                  str(handle)), file=sys.stderr)
+            return
+        try:
+            source = models.Source.objects.get(handle=source_handle)
+        except:
+            print(("ERROR: Source does not exist: '%s'" % 
+                                  str(source_handle)), file=sys.stderr)
+            return
+        citation.source = source
+        self.add_date(citation, date)
+        citation.save(save_cache=False)
+        self.add_note_list(citation, note_list) 
+        self.add_media_ref_list(citation, media_list) 
+        self.add_citation_attribute_list(citation, attribute_list)
+        self.add_tag_list(citation, tag_list)
+        citation.save_cache()
+
+    def add_child_ref_default(self, obj, child, frel=1, mrel=1, private=False):
+        object_type = ContentType.objects.get_for_model(obj) # obj is family
+        count = models.ChildRef.objects.filter(object_id=obj.id,object_type=object_type).count()
+        child_ref = models.ChildRef(private=private,
+                                referenced_by=obj,
+                                ref_object=child,
+                                order=count + 1,
+                                father_rel_type=models.get_type(models.ChildRefType, frel),  # birth
+                                mother_rel_type=models.get_type(models.ChildRefType, mrel))
+        child_ref.save()
+
+    def add_child_ref(self, obj, data):
+        (private, citation_list, note_list, ref, frel, mrel) = data
+        try:
+            child = models.Person.objects.get(handle=ref)
+        except:
+            print(("ERROR: Person does not exist: '%s'" % 
+                                  str(ref)), file=sys.stderr)
+            return
+        object_type = ContentType.objects.get_for_model(obj)
+        count = models.ChildRef.objects.filter(object_id=obj.id,object_type=object_type).count()
+        child_ref = models.ChildRef(private=private,
+                                referenced_by=obj,
+                                ref_object=child,
+                                order=count + 1,
+                                father_rel_type=models.get_type(models.ChildRefType, frel),
+                                mother_rel_type=models.get_type(models.ChildRefType, mrel))
+        child_ref.save()
+        self.add_citation_list(child_ref, citation_list)
+        self.add_note_list(child_ref, note_list)
+    
+    def add_event_ref_default(self, obj, event, private=False, role=models.EventRoleType._DEFAULT):
+        object_type = ContentType.objects.get_for_model(obj)
+        count = models.EventRef.objects.filter(object_id=obj.id,object_type=object_type).count()
+        event_ref = models.EventRef(private=private,
+                                referenced_by=obj,
+                                ref_object=event,
+                                order=count + 1,
+                                role_type = models.get_type(models.EventRoleType, role))
+        event_ref.save()
+
+    def add_event_ref(self, obj, event_data):
+        (private, note_list, attribute_list, ref, role) = event_data
+        try:
+            event = models.Event.objects.get(handle=ref)
+        except:
+            print(("ERROR: Event does not exist: '%s'" % 
+                                  str(ref)), file=sys.stderr)
+            return
+        object_type = ContentType.objects.get_for_model(obj)
+        count = models.EventRef.objects.filter(object_id=obj.id,object_type=object_type).count()
+        event_ref = models.EventRef(private=private,
+                                referenced_by=obj,
+                                ref_object=event,
+                                order=count + 1,
+                                role_type = models.get_type(models.EventRoleType, role))
+        event_ref.save()
+        self.add_note_list(event_ref, note_list)
+        self.add_attribute_list(event_ref, attribute_list)
+
+    def add_repository_ref_default(self, obj, repository, private=False, call_number="", 
+                                   source_media_type=models.SourceMediaType._DEFAULT):
+        object_type = ContentType.objects.get_for_model(obj)
+        count = models.RepositoryRef.objects.filter(object_id=obj.id,object_type=object_type).count()
+        repos_ref = models.RepositoryRef(private=private,
+                                         referenced_by=obj,
+                                         call_number=call_number,
+                                         source_media_type=models.get_type(models.SourceMediaType,
+                                                                           source_media_type),
+                                         ref_object=repository,
+                                         order=count + 1)
+        repos_ref.save()
+    
+    def add_repository_ref(self, obj, reporef_data):
+        (note_list, 
+         ref,
+         call_number, 
+         source_media_type,
+         private) = reporef_data
+        try:
+            repository = models.Repository.objects.get(handle=ref)
+        except:
+            print(("ERROR: Repository does not exist: '%s'" % 
+                                  str(ref)), file=sys.stderr)
+            return
+        object_type = ContentType.objects.get_for_model(obj)
+        count = models.RepositoryRef.objects.filter(object_id=obj.id,object_type=object_type).count()
+        repos_ref = models.RepositoryRef(private=private,
+                                     referenced_by=obj,
+                                     call_number=call_number,
+                                     source_media_type=models.get_type(models.SourceMediaType,
+                                                                source_media_type),
+                                     ref_object=repository,
+                                     order=count + 1)
+        repos_ref.save()
+        self.add_note_list(repos_ref, note_list)
+    
+    def add_family_ref(self, obj, handle):
+        try:
+            family = models.Family.objects.get(handle=handle)
+        except:
+            print(("ERROR: Family does not exist: '%s'" % 
+                                  str(handle)), file=sys.stderr)
+            return
+        #obj.families.add(family)
+        pfo = models.MyFamilies(person=obj, family=family,
+                                order=len(models.MyFamilies.objects.filter(person=obj)) + 1)
+        pfo.save()
+        obj.save()
+    
+    ## Export individual objects:
+    
+    def add_source_attribute_list(self, source, attribute_list):
+        ## FIXME: dict to list
+        count = 1
+        #for key in datamap_dict:
+        #    value = datamap_dict[key]
+        #    datamap = models.SourceDatamap(key=key, value=value, order=count)
+        #    datamap.source = source
+        #    datamap.save()
+        #    count += 1
+    
+    def add_citation_attribute_list(self, citation, attribute_list):
+        ## FIXME: dict to list
+        count = 1
+        #for key in datamap_dict:
+        #    value = datamap_dict[key]
+        #    datamap = models.CitationDatamap(key=key, value=value, order=count)
+        #    datamap.citation = citation
+        #    datamap.save()
+        #    count += 1
+    
+    def add_lds(self, field, obj, data, order):
+        (lcitation_list, lnote_list, date, type, place_handle,
+         famc_handle, temple, status, private) = data
+        if place_handle:
+            try:
+                place = models.Place.objects.get(handle=place_handle)
+            except:
+                print(("ERROR: Place does not exist: '%s'" % 
+                                      str(place_handle)), file=sys.stderr)
+                place = None
+        else:
+            place = None
+        if famc_handle:
+            try:
+                famc = models.Family.objects.get(handle=famc_handle)
+            except:
+                print(("ERROR: Family does not exist: '%s'" % 
+                                      str(famc_handle)), file=sys.stderr)
+                famc = None
+        else:
+            famc = None
+        lds = models.Lds(lds_type = models.get_type(models.LdsType, type),
+                     temple=temple, 
+                     place=place,
+                     famc=famc,
+                     order=order,
+                     status = models.get_type(models.LdsStatus, status),
+                     private=private)
+        self.add_date(lds, date)
+        lds.save()
+        self.add_note_list(lds, lnote_list)
+        self.add_citation_list(lds, lcitation_list)
+        if field == "person":
+            lds.person = obj
+        elif field == "family":
+            lds.family = obj
+        else:
+            raise AttributeError("invalid field '%s' to attach lds" %
+                                 str(field))
+        lds.save()
+        return lds
+    
+    def add_address(self, field, obj, address_data, order):
+        (private, acitation_list, anote_list, date, location) = address_data
+        address = models.Address(private=private, order=order)
+        self.add_date(address, date)
+        address.save()
+        self.add_location("address", address, location, 1)
+        self.add_note_list(address, anote_list) 
+        self.add_citation_list(address, acitation_list)
+        if field == "person":
+            address.person = obj
+        elif field == "repository":
+            address.repository = obj
+        else:
+            raise AttributeError("invalid field '%s' to attach address" %
+                                 str(field))
+        address.save()
+        #obj.save()
+        #obj.addresses.add(address)
+        #obj.save()
+    
+    def add_attribute(self, obj, attribute_data):
+        (private, citation_list, note_list, the_type, value) = attribute_data
+        attribute_type = models.get_type(models.AttributeType, the_type)
+        attribute = models.Attribute(private=private,
+                                 attribute_of=obj,
+                                 attribute_type=attribute_type,
+                                 value=value)
+        attribute.save()
+        self.add_citation_list(attribute, citation_list)
+        self.add_note_list(attribute, note_list)
+        #obj.attributes.add(attribute)
+        #obj.save()
+    
+    def add_url(self, field, obj, url_data, order):
+        (private, path, desc, type) = url_data
+        url = models.Url(private=private,
+                     path=path,
+                     desc=desc,
+                     order=order,
+                     url_type=models.get_type(models.UrlType, type))
+        if field == "person":
+            url.person = obj
+        elif field == "repository":
+            url.repository = obj
+        elif field == "place":
+            url.place = obj
+        else:
+            raise AttributeError("invalid field '%s' to attach to url" %
+                                 str(field))
+        url.save()
+        #obj.url_list.add(url)
+        #obj.save()
+    
+    def add_place_ref_default(self, obj, place, date=None):
+        count = place.references.count()
+        object_type = ContentType.objects.get_for_model(obj)
+        count = models.PlaceRef.objects.filter(object_id=obj.id,
+                                               object_type=object_type).count()
+        place_ref = models.PlaceRef(referenced_by=obj,
+                                    ref_object=place,
+                                    order=count + 1)
+        self.add_date(obj, date)
+        place_ref.save()
+    
+    def add_place_ref(self, obj, data):
+        place_handle, date = data
+        if place_handle:
+            try:
+                place = models.Place.objects.get(handle=place_handle)
+            except:
+                print(("ERROR: Place does not exist: '%s'" % str(place_handle)), file=sys.stderr)
+                #from gramps.gen.utils.debug import format_exception
+                #print("".join(format_exception()), file=sys.stderr)
+                return
+        object_type = ContentType.objects.get_for_model(obj)
+        count = models.PlaceRef.objects.filter(object_id=obj.id,object_type=object_type).count()
+        place_ref = models.PlaceRef(referenced_by=obj, ref_object=place, order=count + 1)
+        place_ref.save()
+        self.add_date(place_ref, date)
+
+    def add_parent_family(self, person, parent_family_handle):
+        try:
+            family = models.Family.objects.get(handle=parent_family_handle)
+        except:
+            print(("ERROR: Family does not exist: '%s'" % 
+                                  str(parent_family_handle)), file=sys.stderr)
+            return
+        #person.parent_families.add(family)
+        pfo = models.MyParentFamilies(
+            person=person, 
+            family=family,
+            order=len(models.MyParentFamilies.objects.filter(person=person)) + 1)
+        pfo.save()
+        person.save()
+    
+    def add_date(self, obj, date):
+        if date is None: 
+            (calendar, modifier, quality, text, sortval, newyear) = \
+                (0, 0, 0, "", 0, 0)
+            day1, month1, year1, slash1 = 0, 0, 0, 0
+            day2, month2, year2, slash2 = 0, 0, 0, 0
+        else:
+            (calendar, modifier, quality, dateval, text, sortval, newyear) = date
+            if len(dateval) == 4:
+                day1, month1, year1, slash1 = dateval
+                day2, month2, year2, slash2 = 0, 0, 0, 0
+            elif len(dateval) == 8:
+                day1, month1, year1, slash1, day2, month2, year2, slash2 = dateval
+            else:
+                raise AttributeError("ERROR: dateval format '%s'" % str(dateval))
+        obj.calendar = calendar
+        obj.modifier = modifier
+        obj.quality = quality
+        obj.text = text
+        obj.sortval = sortval
+        obj.newyear = newyear
+        obj.day1 = day1
+        obj.month1 = month1
+        obj.year1 = year1
+        obj.slash1 = slash1
+        obj.day2 = day2
+        obj.month2 = month2
+        obj.year2 = year2
+        obj.slash2 = slash2
+    
+    def add_name(self, person, data, preferred):
+        if data:
+            (private, citation_list, note_list, date,
+             first_name, surname_list, suffix, title,
+             name_type, group_as, sort_as, 
+             display_as, call, nick, famnick) = data
+    
+            count = person.name_set.count()
+            name = models.Name()
+            name.order = count + 1
+            name.preferred = preferred
+            name.private = private
+            name.first_name = first_name
+            name.suffix = suffix
+            name.title = title
+            name.name_type = models.get_type(models.NameType, name_type)
+            name.group_as = group_as
+            name.sort_as = models.get_type(models.NameFormatType, sort_as)
+            name.display_as = models.get_type(models.NameFormatType, display_as)
+            name.call = call
+            name.nick = nick
+            name.famnick = famnick
+            # we know person exists
+            # needs to have an ID for key
+            name.person = person
+            self.add_date(name, date) 
+            name.save()
+            self.add_surname_list(name, surname_list)
+            self.add_note_list(name, note_list)
+            self.add_citation_list(name, citation_list)
+            #person.save()
+           
+    ## Export primary objects:
+    
+    def add_person(self, data):
+        # Unpack from the BSDDB:
+        (handle,        #  0
+         gid,          #  1
+         gender,             #  2
+         primary_name,       #  3
+         alternate_names,    #  4
+         death_ref_index,    #  5
+         birth_ref_index,    #  6
+         event_ref_list,     #  7
+         family_list,        #  8
+         parent_family_list, #  9
+         media_list,         # 10
+         address_list,       # 11
+         attribute_list,     # 12
+         url_list,               # 13
+         lds_ord_list,       # 14
+         pcitation_list,       # 15
+         pnote_list,         # 16
+         change,             # 17
+         tag_list,             # 18
+         private,           # 19
+         person_ref_list,    # 20
+         ) = data
+    
+        person = models.Person(handle=handle,
+                            gramps_id=gid,
+                            last_changed=todate(change),
+                            private=private,
+                            gender_type=models.get_type(models.GenderType, gender))
+        person.save(save_cache=False)
+
+    def add_person_detail(self, data):
+        # Unpack from the BSDDB:
+        (handle,        #  0
+         gid,          #  1
+         gender,             #  2
+         primary_name,       #  3
+         alternate_names,    #  4
+         death_ref_index,    #  5
+         birth_ref_index,    #  6
+         event_ref_list,     #  7
+         family_list,        #  8
+         parent_family_list, #  9
+         media_list,         # 10
+         address_list,       # 11
+         attribute_list,     # 12
+         url_list,               # 13
+         lds_ord_list,       # 14
+         pcitation_list,       # 15
+         pnote_list,         # 16
+         change,             # 17
+         tag_list,             # 18
+         private,           # 19
+         person_ref_list,    # 20
+         ) = data
+    
+        try:
+            person = models.Person.objects.get(handle=handle)
+        except:
+            print(("ERROR: Person does not exist: '%s'" % 
+                                  str(handle)), file=sys.stderr)
+            return
+        if primary_name:
+            self.add_name(person, primary_name, True)
+        self.add_alternate_name_list(person, alternate_names)
+        self.add_event_ref_list(person, event_ref_list)
+        self.add_family_ref_list(person, family_list) 
+        self.add_parent_family_list(person, parent_family_list)
+        self.add_media_ref_list(person, media_list)
+        self.add_note_list(person, pnote_list)
+        self.add_attribute_list(person, attribute_list)
+        self.add_url_list("person", person, url_list) 
+        self.add_person_ref_list(person, person_ref_list)
+        self.add_citation_list(person, pcitation_list)
+        self.add_address_list("person", person, address_list)
+        self.add_lds_list("person", person, lds_ord_list)
+        self.add_tag_list(person, tag_list)
+        # set person.birth and birth.death to correct events:
+
+        obj_type = ContentType.objects.get_for_model(person)
+        events = models.EventRef.objects.filter(
+            object_id=person.id, 
+            object_type=obj_type, 
+            ref_object__event_type__val=models.EventType.BIRTH).order_by("order")
+
+        all_events = self.get_event_ref_list(person)
+        if events:
+            person.birth = events[0].ref_object
+            person.birth_ref_index = lookup_role_index(models.EventType.BIRTH, all_events)
+
+        events = models.EventRef.objects.filter(
+            object_id=person.id, 
+            object_type=obj_type, 
+            ref_object__event_type__val=models.EventType.DEATH).order_by("order")
+        if events:
+            person.death = events[0].ref_object
+            person.death_ref_index = lookup_role_index(models.EventType.DEATH, all_events)
+        person.save()
+        return person
+
+    def save_note_markup(self, note, markup_list):
+        # delete any prexisting markup:
+        models.Markup.objects.filter(note=note).delete()
+        count = 1
+        for markup in markup_list:
+            markup_code, value, start_stop_list = markup
+            m = models.Markup(
+                note=note, 
+                order=count, 
+                styled_text_tag_type=models.get_type(models.StyledTextTagType,
+                                                     markup_code, 
+                                                     get_or_create=False),
+                string=value,
+                start_stop_list=str(start_stop_list))
+            m.save()
+    
+    def add_note(self, data):
+        # Unpack from the BSDDB:
+        (handle, gid, styled_text, format, note_type,
+         change, tag_list, private) = data
+        text, markup_list = styled_text
+        n = models.Note(handle=handle,
+                        gramps_id=gid,
+                        last_changed=todate(change),
+                        private=private,
+                        preformatted=format,
+                        text=text,
+                        note_type=models.get_type(models.NoteType, note_type))
+        n.save(save_cache=False)
+        self.save_note_markup(n, markup_list)
+    
+    def add_note_detail(self, data):
+        # Unpack from the BSDDB:
+        (handle, gid, styled_text, format, note_type,
+         change, tag_list, private) = data
+        note = models.Note.objects.get(handle=handle)
+        note.save(save_cache=False)
+        self.add_tag_list(note, tag_list)
+        note.save_cache()
+
+    def add_family(self, data):
+        # Unpack from the BSDDB:
+        (handle, gid, father_handle, mother_handle,
+         child_ref_list, the_type, event_ref_list, media_list,
+         attribute_list, lds_seal_list, citation_list, note_list,
+         change, tag_list, private) = data
+    
+        family = models.Family(handle=handle, gramps_id=gid, 
+                               family_rel_type = models.get_type(models.FamilyRelType, the_type),
+                               last_changed=todate(change), 
+                               private=private)
+        family.save(save_cache=False)
+
+    def add_family_detail(self, data):
+        # Unpack from the BSDDB:
+        (handle, gid, father_handle, mother_handle,
+         child_ref_list, the_type, event_ref_list, media_list,
+         attribute_list, lds_seal_list, citation_list, note_list,
+         change, tag_list, private) = data
+    
+        try:
+            family = models.Family.objects.get(handle=handle)
+        except:
+            print(("ERROR: Family does not exist: '%s'" % 
+                                  str(handle)), file=sys.stderr)
+            return
+        # father_handle and/or mother_handle can be None
+        if father_handle:
+            try:
+                family.father = models.Person.objects.get(handle=father_handle)
+            except:
+                print(("ERROR: Father does not exist: '%s'" % 
+                                      str(father_handle)), file=sys.stderr)
+                family.father = None
+        if mother_handle:
+            try:
+                family.mother = models.Person.objects.get(handle=mother_handle)
+            except:
+                print(("ERROR: Mother does not exist: '%s'" % 
+                                      str(mother_handle)), file=sys.stderr)
+                family.mother = None
+        family.save(save_cache=False)
+        self.add_child_ref_list(family, child_ref_list)
+        self.add_note_list(family, note_list)
+        self.add_attribute_list(family, attribute_list)
+        self.add_citation_list(family, citation_list)
+        self.add_media_ref_list(family, media_list)
+        self.add_event_ref_list(family, event_ref_list)
+        self.add_lds_list("family", family, lds_seal_list)
+        self.add_tag_list(family, tag_list)
+        family.save_cache()
+        
+    def add_source(self, data):
+        (handle, gid, title,
+         author, pubinfo,
+         note_list,
+         media_list,
+         abbrev,
+         change, attribute_list,
+         reporef_list,
+         tag_list,
+         private) = data
+        source = models.Source(handle=handle, gramps_id=gid, title=title,
+                               author=author, pubinfo=pubinfo, abbrev=abbrev,
+                               last_changed=todate(change), private=private)
+        source.save(save_cache=False)
+
+    def add_source_detail(self, data):
+        (handle, gid, title,
+         author, pubinfo,
+         note_list,
+         media_list,
+         abbrev,
+         change, attribute_list,
+         reporef_list,
+         tag_list,
+         private) = data
+        try:
+            source = models.Source.objects.get(handle=handle)
+        except:
+            print(("ERROR: Source does not exist: '%s'" % 
+                                  str(handle)), file=sys.stderr)
+            return
+        source.save(save_cache=False)
+        self.add_note_list(source, note_list) 
+        self.add_media_ref_list(source, media_list)
+        self.add_source_attribute_list(source, attribute_list)
+        self.add_repository_ref_list(source, reporef_list)
+        self.add_tag_list(source, tag_list)
+        source.save_cache()
+    
+    def add_repository(self, data):
+        (handle, gid, the_type, name, note_list,
+         address_list, url_list, change, tag_list, private) = data
+    
+        repository = models.Repository(handle=handle,
+                                       gramps_id=gid,
+                                       last_changed=todate(change), 
+                                       private=private,
+                                       repository_type=models.get_type(models.RepositoryType, the_type),
+                                       name=name)
+        repository.save(save_cache=False)
+
+    def add_repository_detail(self, data):
+        (handle, gid, the_type, name, note_list,
+         address_list, url_list, change, tag_list, private) = data
+        try:
+            repository = models.Repository.objects.get(handle=handle)
+        except:
+            print(("ERROR: Repository does not exist: '%s'" % 
+                                  str(handle)), file=sys.stderr)
+            return
+        repository.save(save_cache=False)
+        self.add_note_list(repository, note_list)
+        self.add_url_list("repository", repository, url_list)
+        self.add_address_list("repository", repository, address_list)
+        self.add_tag_list(repository, tag_list)
+        repository.save_cache()
+    
+    def add_location(self, field, obj, location_data, order):
+        # location now has 8 items
+        # street, locality, city, county, state,
+        # country, postal, phone, parish
+
+        if location_data == None: return
+        if len(location_data) == 8:
+            (street, locality, city, county, state, country, postal, phone) = location_data
+            parish = None
+        elif len(location_data) == 2:
+            ((street, locality, city, county, state, country, postal, phone), parish) = location_data
+        else:
+            print(("ERROR: unknown location: '%s'" % 
+                                  str(location_data)), file=sys.stderr)
+            (street, locality, city, county, state, country, postal, phone, parish) = \
+                ("", "", "", "", "", "", "", "", "")
+        location = models.Location(street = street,
+                                   locality = locality,
+                               city = city,
+                               county = county,
+                               state = state,
+                               country = country,
+                               postal = postal,
+                               phone = phone,
+                               parish = parish,
+                               order = order)
+        if field == "address":
+            location.address = obj
+        elif field == "place":
+            location.place = obj
+        else:
+            raise AttributeError("invalid field '%s' to attach to location" %
+                                 str(field))
+        location.save()
+        #obj.locations.add(location)
+        #obj.save()
+    
+    def add_place(self, data):
+        ## ('cef246c95c132bcf6a0255d4d17', 'P0036', 'Santa Clara Co., CA, USA', '', '', [('cef243fb5634559442323368f63', None)], 'Santa Clara Co.', [], (3, ''), '', [], [], [], [], [], 1422124781, [], False)
+        (handle, gid, title, long, lat,
+         place_ref_list,
+         name,
+         alt_name_list,
+         place_type,
+         code,
+         alt_location_list,
+         url_list,
+         media_list,
+         citation_list,
+         note_list,
+         change, 
+         tag_list,
+         private) = data
+        place = models.Place(
+            handle=handle, 
+            gramps_id=gid, 
+            title=title,
+            long=long, 
+            lat=lat, 
+            name=name,
+            place_type=models.get_type(models.PlaceType, place_type), 
+            code=code, 
+            last_changed=todate(change),
+            private=private)
+        try:
+            place.save(save_cache=False)
+        except:
+            print("FIXME: error in saving place")
+
+    def add_place_detail(self, data):
+        (handle, gid, title, long, lat,
+         place_ref_list,
+         name,
+         alt_name_list,
+         place_type,
+         code,
+         alt_location_list,
+         url_list,
+         media_list,
+         citation_list,
+         note_list,
+         change, 
+         tag_list,
+         private) = data
+        try:
+            place = models.Place.objects.get(handle=handle)
+        except:
+            print(("ERROR: Place does not exist: '%s'" % 
+                                  str(handle)), file=sys.stderr)
+            return
+        place.save(save_cache=False)
+        self.add_url_list("place", place, url_list)
+        self.add_media_ref_list(place, media_list)
+        self.add_citation_list(place, citation_list)
+        self.add_note_list(place, note_list) 
+        self.add_tag_list(place, tag_list)
+        self.add_place_ref_list(place, place_ref_list)
+        self.add_alt_name_list(place, alt_name_list)
+        count = 1
+        for loc_data in alt_location_list:
+            self.add_location("place", place, loc_data, count)
+            count + 1
+        place.save_cache()
+
+    def add_tag(self, data):
+        (handle,
+         name,
+         color,
+         priority,
+         change) = data
+        tag = models.Tag(handle=handle,
+                         gramps_id=create_id(),
+                         name=name,
+                         color=color,
+                         priority=priority,
+                         last_changed=todate(change))
+        tag.save(save_cache=False)
+    
+    def add_tag_detail(self, data):
+        (handle,
+         name,
+         color,
+         priority,
+         change) = data
+        tag = models.Tag.objects.get(handle=handle)
+        tag.save()
+
+    def add_media(self, data):
+        (handle, gid, path, mime, desc,
+         checksum,
+         attribute_list,
+         citation_list,
+         note_list,
+         change,
+         date,
+         tag_list,
+         private) = data
+        media = models.Media(handle=handle, gramps_id=gid,
+                             path=path, mime=mime, checksum=checksum,
+                             desc=desc, last_changed=todate(change),
+                             private=private)
+        self.add_date(media, date)
+        media.save(save_cache=False)
+    
+    def add_media_detail(self, data):
+        (handle, gid, path, mime, desc,
+         checksum,
+         attribute_list,
+         citation_list,
+         note_list,
+         change,
+         date,
+         tag_list,
+         private) = data
+        try:
+            media = models.Media.objects.get(handle=handle)
+        except:
+            print(("ERROR: Media does not exist: '%s'" % 
+                                  str(handle)), file=sys.stderr)
+            return
+        media.save(save_cache=False)
+        self.add_note_list(media, note_list) 
+        self.add_citation_list(media, citation_list)
+        self.add_attribute_list(media, attribute_list)
+        self.add_tag_list(media, tag_list)
+        media.save_cache()
+    
+    def add_event(self, data):
+        (handle, gid, the_type, date, description, place_handle, 
+         citation_list, note_list, media_list, attribute_list,
+         change, tag_list, private) = data
+        event = models.Event(handle=handle,
+                             gramps_id=gid, 
+                             event_type=models.get_type(models.EventType, the_type),
+                             private=private,
+                             description=description,
+                             last_changed=todate(change))
+        self.add_date(event, date)
+        event.save(save_cache=False)
+
+    def add_event_detail(self, data):
+        (handle, gid, the_type, date, description, place_handle, 
+         citation_list, note_list, media_list, attribute_list,
+         change, tag_list, private) = data
+        try:
+            event = models.Event.objects.get(handle=handle)
+        except:
+            print(("ERROR: Event does not exist: '%s'" % 
+                                  str(handle)), file=sys.stderr)
+            return
+        try:
+            place = models.Place.objects.get(handle=place_handle)
+        except:
+            place = None
+            print(("ERROR: Place does not exist: '%s'" % 
+                                  str(place_handle)), file=sys.stderr)
+        event.place = place
+        event.save(save_cache=False)
+        self.add_note_list(event, note_list)
+        self.add_attribute_list(event, attribute_list)
+        self.add_media_ref_list(event, media_list)
+        self.add_citation_list(event, citation_list)
+        self.add_tag_list(event, tag_list)
+        event.save_cache()
+
+    def get_raw(self, item):
+        """
+        Build and return the raw, serialized data of an object.
+        """
+        if isinstance(item, models.Person):
+            raw = self.get_person(item)
+        elif isinstance(item, models.Family):
+            raw = self.get_family(item)
+        elif isinstance(item, models.Place):
+            raw = self.get_place(item)
+        elif isinstance(item, models.Media):
+            raw = self.get_media(item)
+        elif isinstance(item, models.Source):
+            raw = self.get_source(item)
+        elif isinstance(item, models.Citation):
+            raw = self.get_citation(item)
+        elif isinstance(item, models.Repository):
+            raw = self.get_repository(item)
+        elif isinstance(item, models.Note):
+            raw = self.get_note(item)
+        elif isinstance(item, models.Event):
+            raw = self.get_event(item)
+        else:
+            raise Exception("Don't know how to get raw '%s'" % type(item))
+        return raw
+
+    def check_caches(self, callback=None):
+        """
+        Call this to check the caches for all primary models.
+        """
+        if not isinstance(callback, collections.Callable): 
+            callback = lambda percent: None # dummy
+
+        callback(0)
+        count = 0.0
+        total = (self.Note.all().count() + 
+                 self.Person.all().count() +
+                 self.Event.all().count() + 
+                 self.Family.all().count() +
+                 self.Repository.all().count() +
+                 self.Place.all().count() +
+                 self.Media.all().count() +
+                 self.Source.all().count() +
+                 self.Citation.all().count() +
+                 self.Tag.all().count())
+
+        for item in self.Note.all():
+            raw = self.get_note(item)
+            check_diff(item, raw)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Person.all():
+            raw = self.get_person(item)
+            check_diff(item, raw)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Family.all():
+            raw = self.get_family(item)
+            check_diff(item, raw)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Source.all():
+            raw = self.get_source(item)
+            check_diff(item, raw)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Event.all():
+            raw = self.get_event(item)
+            check_diff(item, raw)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Repository.all():
+            raw = self.get_repository(item)
+            check_diff(item, raw)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Place.all():
+            raw = self.get_place(item)
+            check_diff(item, raw)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Media.all():
+            raw = self.get_media(item)
+            check_diff(item, raw)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Citation.all():
+            raw = self.get_citation(item)
+            check_diff(item, raw)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Tag.all():
+            raw = self.get_tag(item)
+            check_diff(item, raw)
+            count += 1
+        callback(100)
+
+    def check_families(self):
+        """
+        Check family structures.
+        """
+        for family in self.Family.all():
+            if family.mother:
+                if not family in family.mother.families.all():
+                    print("Mother not in family", mother, family)
+            if family.father:
+                if not family in family.father.families.all():
+                    print("Father not in family", mother, family)
+            for child in family.get_children():
+                if family not in child.parent_families.all():
+                    print("Child not in family", child, family)
+        for person in self.Person.all():
+            for family in person.families.all():
+                if person not in [family.mother, family.father]:
+                    print("Spouse not in family", person, family)
+            for family in person.parent_families.all():
+                if person not in family.get_children():
+                    print("Child not in family", person, family)
+
+    def is_public(self, obj, objref):
+        """
+        Returns whether or not an item is "public", and the reason
+        why/why not.
+
+        @param obj - an instance of any Primary object
+        @param objref - one of the PrimaryRef.objects 
+        @return - a tuple containing a boolean (public?) and reason.
+
+        There are three reasons why an item might not be public:
+           1) The item itself is private.
+           2) The item is referenced by a living Person.
+           3) The item is referenced by some other private item.
+        """
+        # If it is private, then no:
+        if obj.private:
+            return (False, "It is marked private.")
+        elif hasattr(obj, "probably_alive") and obj.probably_alive:
+            return (False, "It is marked probaby alive.")
+        elif hasattr(obj, "mother") and obj.mother:
+            public, reason = self.is_public(obj.mother, self.PersonRef)
+            if not public:
+                return public, reason
+        elif hasattr(obj, "father") and obj.father:
+            public, reason = self.is_public(obj.father, self.PersonRef)
+            if not public:
+                return public, reason
+        # FIXME: what about Associations... anything else? Check PrivateProxy
+        if objref:
+            if hasattr(objref.model, "ref_object"):
+                obj_ref_list = objref.filter(ref_object=obj)
+            elif hasattr(objref.model, "citation"):
+                obj_ref_list = objref.filter(citation=obj)
+            else:
+                raise Exception("objref '%s' needs a ref for '%s'" % (objref.model, obj))
+            for reference in obj_ref_list:
+                ref_from_class = reference.object_type.model_class()
+                item = None
+                try:
+                    item = ref_from_class.objects.get(id=reference.object_id)
+                except:
+                    print("Warning: Corrupt reference: %s" % str(reference))
+                    continue
+                # If it is linked to by someone alive? public = False
+                if hasattr(item, "probably_alive") and item.probably_alive:
+                    return (False, "It is referenced by someone who is probaby alive.")
+                # If it is linked to by something private? public = False
+                elif item.private:
+                    return (False, "It is referenced by an item which is marked private.")
+        return (True, "It is visible to the public.")
+
+    def update_public(self, obj, save=True):
+        """
+        >>> dji.update_public(event)
+
+        Given an Event or other instance, update the event's public
+        status, or any event referenced to by the instance.
+
+        For example, if a person is found to be alive, then the
+        referenced events should be marked not public (public = False).
+
+        """
+        from gramps.webapp.utils import probably_alive
+        if obj.__class__.__name__ == "Event":
+            objref = self.EventRef
+        elif obj.__class__.__name__ == "Person":
+            objref = self.PersonRef 
+        elif obj.__class__.__name__ == "Note":
+            objref = self.NoteRef
+        elif obj.__class__.__name__ == "Repository":
+            objref = self.RepositoryRef
+        elif obj.__class__.__name__ == "Citation":
+            objref = self.CitationRef
+        elif obj.__class__.__name__ == "Media":
+            objref = self.MediaRef
+        elif  obj.__class__.__name__ == "Place": # no need for dependency
+            objref = None
+        elif  obj.__class__.__name__ == "Source": # no need for dependency
+            objref = None
+        elif  obj.__class__.__name__ == "Family":
+            objref = self.ChildRef # correct?
+        else:
+            raise Exception("Can't compute public of type '%s'" % str(obj))
+        public, reason = self.is_public(obj, objref) # correct?
+        # Ok, update, if needed:
+        if obj.public != public:
+            obj.public = public
+            if save:
+                print("Updating public:", obj.__class__.__name__, obj.gramps_id)
+                obj.save()
+                #log = self.Log()
+                #log.referenced_by = obj
+                #log.object_id = obj.id
+                #log.object_type = obj_type
+                #log.log_type = "update public status"
+                #log.reason = reason
+                #log.order = 0
+                #log.save()
+
+    def update_publics(self, callback=None):
+        """
+        Call this to update probably_alive for all primary models.
+        """
+        if not isinstance(callback, collections.Callable): 
+            callback = lambda percent: None # dummy
+
+        callback(0)
+        count = 0.0
+        total = (self.Note.all().count() + 
+                 self.Person.all().count() +
+                 self.Event.all().count() + 
+                 self.Family.all().count() +
+                 self.Repository.all().count() +
+                 self.Place.all().count() +
+                 self.Media.all().count() +
+                 self.Source.all().count() +
+                 self.Citation.all().count())
+
+        for item in self.Note.all():
+            self.update_public(item)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Person.all():
+            self.update_public(item)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Family.all():
+            self.update_public(item)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Source.all():
+            self.update_public(item)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Event.all():
+            self.update_public(item)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Repository.all():
+            self.update_public(item)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Place.all():
+            self.update_public(item)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Media.all():
+            self.update_public(item)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Citation.all():
+            self.update_public(item)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+    def update_probably_alive(self, callback=None):
+        """
+        Call this to update primary_alive for people.
+        """
+        from gramps.webapp.utils import probably_alive
+        if not isinstance(callback, collections.Callable): 
+            callback = lambda percent: None # dummy
+        callback(0)
+        count = 0.0
+        total = self.Person.all().count()
+        for item in self.Person.all():
+            pa = probably_alive(item.handle)
+            if pa != item.probably_alive:
+                print("Updating probably_alive")
+                item.probably_alive = pa
+                item.save()
+            count += 1
+        callback(100 * (count/total if total else 0))
diff --git a/gramps/plugins/database/djangodb.gpr.py b/gramps/plugins/database/djangodb.gpr.py
new file mode 100644
index 000000000..57b30f2f5
--- /dev/null
+++ b/gramps/plugins/database/djangodb.gpr.py
@@ -0,0 +1,31 @@
+#
+# Gramps - a GTK+/GNOME based genealogy program
+#
+# Copyright (C) 2015 Douglas Blank <doug.blank@gmail.com>
+#
+# 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+plg = newplugin()
+plg.id    = 'djangodb'
+plg.name  = _("Django Database Backend")
+plg.name_accell  = _("_Django Database Backend")
+plg.description =  _("Django Object Relational Model Database Backend")
+plg.version = '1.0'
+plg.gramps_target_version = "4.2"
+plg.status = STABLE
+plg.fname = 'djangodb.py'
+plg.ptype = DATABASE
+plg.databaseclass = 'DbDjango'
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
new file mode 100644
index 000000000..adb28dc54
--- /dev/null
+++ b/gramps/plugins/database/djangodb.py
@@ -0,0 +1,2036 @@
+# Gramps - a GTK+/GNOME based genealogy program
+#
+# Copyright (C) 2009         Douglas S. Blank <doug.blank@gmail.com>
+#
+# 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+""" Implements a Db interface """
+
+#------------------------------------------------------------------------
+#
+# Python Modules
+#
+#------------------------------------------------------------------------
+import sys
+import time
+import re
+import base64
+import pickle
+import os
+
+#------------------------------------------------------------------------
+#
+# Gramps Modules
+#
+#------------------------------------------------------------------------
+import gramps
+from gramps.gen.lib import (Person, Family, Event, Place, Repository, 
+                            Citation, Source, Note, MediaObject, Tag, 
+                            Researcher)
+from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn
+from gramps.gen.utils.callback import Callback
+from gramps.gen.updatecallback import UpdateCallback
+from gramps.gen.db import (PERSON_KEY,
+                    FAMILY_KEY,
+                    CITATION_KEY,
+                    SOURCE_KEY,
+                    EVENT_KEY,
+                    MEDIA_KEY,
+                    PLACE_KEY,
+                    REPOSITORY_KEY,
+                    NOTE_KEY)
+from gramps.gen.utils.id import create_id
+from django.db import transaction
+
+class Environment(object):
+    """
+    Implements the Environment API.
+    """
+    def __init__(self, db):
+        self.db = db
+
+    def txn_begin(self):
+        return DjangoTxn("DbDjango Transaction", self.db)
+
+class Table(object):
+    """
+    Implements Table interface.
+    """
+    def __init__(self, funcs):
+        self.funcs = funcs
+
+    def cursor(self):
+        """
+        Returns a Cursor for this Table.
+        """
+        return self.funcs["cursor_func"]()
+
+    def put(key, data, txn=None):
+        self[key] = data
+
+class Map(dict):
+    """
+    Implements the map API for person_map, etc.
+    
+    Takes a Table() as argument.
+    """
+    def __init__(self, tbl, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.db = tbl
+
+class MetaCursor(object):
+    def __init__(self):
+        pass
+    def __enter__(self):
+        return self
+    def __iter__(self):
+        return self.__next__()
+    def __next__(self):
+        yield None
+    def __exit__(self, *args, **kwargs):
+        pass
+    def iter(self):
+        yield None
+    def first(self):
+        self._iter = self.__iter__()
+        return self.next()
+    def next(self):
+        try:
+            return next(self._iter)
+        except:
+            return None
+    def close(self):
+        pass
+
+class Cursor(object):
+    def __init__(self, model, func):
+        self.model = model
+        self.func = func
+    def __enter__(self):
+        return self
+    def __iter__(self):
+        return self.__next__()
+    def __next__(self):
+        for item in self.model.all():
+            yield (bytes(item.handle, "utf-8"), self.func(item.handle))
+    def __exit__(self, *args, **kwargs):
+        pass
+    def iter(self):
+        for item in self.model.all():
+            yield (bytes(item.handle, "utf-8"), self.func(item.handle))
+        yield None
+    def first(self):
+        self._iter = self.__iter__()
+        return self.next()
+    def next(self):
+        try:
+            return next(self._iter)
+        except:
+            return None
+    def close(self):
+        pass
+
+class Bookmarks(object):
+    def __init__(self):
+        self.handles = []
+    def get(self):
+        return self.handles
+    def append(self, handle):
+        self.handles.append(handle)
+
+class DjangoTxn(DbTxn):
+    def __init__(self, message, db, table=None):
+        DbTxn.__init__(self, message, db)
+        self.table = table
+
+    def get(self, key, default=None, txn=None, **kwargs):
+        """
+        Returns the data object associated with key
+        """
+        try:
+            return self.table.objects(handle=key)
+        except:
+            if txn and key in txn:
+                return txn[key]
+            else:
+                return None
+
+    def put(self, handle, new_data, txn):
+        """
+        """
+        txn[handle] = new_data
+
+    def commit(self):
+        pass
+
+class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
+    """
+    A Gramps Database Backend. This replicates the grampsdb functions.
+    """
+    # Set up dictionary for callback signal handler
+    # ---------------------------------------------
+    # 1. Signals for primary objects
+    __signals__ = dict((obj+'-'+op, signal)
+                       for obj in
+                       ['person', 'family', 'event', 'place',
+                        'source', 'citation', 'media', 'note', 'repository', 'tag']
+                       for op, signal in zip(
+                               ['add',   'update', 'delete', 'rebuild'],
+                               [(list,), (list,),  (list,),   None]
+                       )
+                   )
+    
+    # 2. Signals for long operations
+    __signals__.update(('long-op-'+op, signal) for op, signal in zip(
+        ['start',  'heartbeat', 'end'],
+        [(object,), None,       None]
+        ))
+
+    # 3. Special signal for change in home person
+    __signals__['home-person-changed'] = None
+
+    # 4. Signal for change in person group name, parameters are
+    __signals__['person-groupname-rebuild'] = (str, str)
+
+    def __init__(self, directory=None):
+        DbReadBase.__init__(self)
+        DbWriteBase.__init__(self)
+        Callback.__init__(self)
+        self._tables = {
+            'Person':
+                {
+                "handle_func": self.get_person_from_handle, 
+                "gramps_id_func": self.get_person_from_gramps_id,
+                "class_func": gramps.gen.lib.Person,
+                "cursor_func": self.get_person_cursor,
+                "handles_func": self.get_person_handles,
+                "iter_func": self.iter_people,
+                },
+            'Family':
+                {
+                "handle_func": self.get_family_from_handle, 
+                "gramps_id_func": self.get_family_from_gramps_id,
+                "class_func": gramps.gen.lib.Family,
+                "cursor_func": self.get_family_cursor,
+                "handles_func": self.get_family_handles,
+                "iter_func": self.iter_families,
+                },
+            'Source':
+                {
+                "handle_func": self.get_source_from_handle, 
+                "gramps_id_func": self.get_source_from_gramps_id,
+                "class_func": gramps.gen.lib.Source,
+                "cursor_func": self.get_source_cursor,
+                "handles_func": self.get_source_handles,
+                "iter_func": self.iter_sources,
+                },
+            'Citation':
+                {
+                "handle_func": self.get_citation_from_handle, 
+                "gramps_id_func": self.get_citation_from_gramps_id,
+                "class_func": gramps.gen.lib.Citation,
+                "cursor_func": self.get_citation_cursor,
+                "handles_func": self.get_citation_handles,
+                "iter_func": self.iter_citations,
+                },
+            'Event':
+                {
+                "handle_func": self.get_event_from_handle, 
+                "gramps_id_func": self.get_event_from_gramps_id,
+                "class_func": gramps.gen.lib.Event,
+                "cursor_func": self.get_event_cursor,
+                "handles_func": self.get_event_handles,
+                "iter_func": self.iter_events,
+                },
+            'Media':
+                {
+                "handle_func": self.get_object_from_handle, 
+                "gramps_id_func": self.get_object_from_gramps_id,
+                "class_func": gramps.gen.lib.MediaObject,
+                "cursor_func": self.get_media_cursor,
+                "handles_func": self.get_media_object_handles,
+                "iter_func": self.iter_media_objects,
+                },
+            'Place':
+                {
+                "handle_func": self.get_place_from_handle, 
+                "gramps_id_func": self.get_place_from_gramps_id,
+                "class_func": gramps.gen.lib.Place,
+                "cursor_func": self.get_place_cursor,
+                "handles_func": self.get_place_handles,
+                "iter_func": self.iter_places,
+                },
+            'Repository':
+                {
+                "handle_func": self.get_repository_from_handle, 
+                "gramps_id_func": self.get_repository_from_gramps_id,
+                "class_func": gramps.gen.lib.Repository,
+                "cursor_func": self.get_repository_cursor,
+                "handles_func": self.get_repository_handles,
+                "iter_func": self.iter_repositories,
+                },
+            'Note':
+                {
+                "handle_func": self.get_note_from_handle, 
+                "gramps_id_func": self.get_note_from_gramps_id,
+                "class_func": gramps.gen.lib.Note,
+                "cursor_func": self.get_note_cursor,
+                "handles_func": self.get_note_handles,
+                "iter_func": self.iter_notes,
+                },
+            'Tag':
+                {
+                "handle_func": self.get_tag_from_handle, 
+                "gramps_id_func": None,
+                "class_func": gramps.gen.lib.Tag,
+                "cursor_func": self.get_tag_cursor,
+                "handles_func": self.get_tag_handles,
+                "iter_func": self.iter_tags,
+                },
+            }
+        # skip GEDCOM cross-ref check for now:
+        self.set_feature("skip-check-xref", True)
+        self.readonly = False
+        self.db_is_open = True
+        self.name_formats = []
+        self.bookmarks = Bookmarks()
+        self.undo_callback = None
+        self.redo_callback = None
+        self.undo_history_callback = None
+        self.modified   = 0
+        self.txn = DjangoTxn("DbDjango Transaction", self)
+        self.transaction = None
+        # Import cache for gedcom import, uses transactions, and
+        # two step adding of objects.
+        self.import_cache = {}
+        self.use_import_cache = False
+        self.use_db_cache = True
+        self._directory = directory
+        if directory:
+            self.load(directory)
+
+    def load(self, directory, pulse_progress=None, mode=None):
+        self._directory = directory
+        from django.conf import settings
+        default_settings = {}
+        settings_file = os.path.join(directory, "default_settings.py")
+        with open(settings_file) as f:
+            code = compile(f.read(), settings_file, 'exec')
+            exec(code, globals(), default_settings)
+
+        class Module(object):
+            def __init__(self, dictionary):
+                self.dictionary = dictionary
+            def __getattr__(self, item):
+                return self.dictionary[item]
+
+        try:
+            settings.configure(Module(default_settings))
+        except RuntimeError:
+            # already configured; ignore
+            pass
+
+        import django
+        django.setup()
+
+        from django_support.libdjango import DjangoInterface
+        self.dji = DjangoInterface()
+        self.family_bookmarks = Bookmarks()
+        self.event_bookmarks = Bookmarks()
+        self.place_bookmarks = Bookmarks()
+        self.citation_bookmarks = Bookmarks()
+        self.source_bookmarks = Bookmarks()
+        self.repo_bookmarks = Bookmarks()
+        self.media_bookmarks = Bookmarks()
+        self.note_bookmarks = Bookmarks()
+        self.set_person_id_prefix('I%04d')
+        self.set_object_id_prefix('O%04d')
+        self.set_family_id_prefix('F%04d')
+        self.set_citation_id_prefix('C%04d')
+        self.set_source_id_prefix('S%04d')
+        self.set_place_id_prefix('P%04d')
+        self.set_event_id_prefix('E%04d')
+        self.set_repository_id_prefix('R%04d')
+        self.set_note_id_prefix('N%04d')
+        # ----------------------------------
+        self.id_trans  = DjangoTxn("ID Transaction", self, self.dji.Person)
+        self.fid_trans = DjangoTxn("FID Transaction", self, self.dji.Family)
+        self.pid_trans = DjangoTxn("PID Transaction", self, self.dji.Place)
+        self.cid_trans = DjangoTxn("CID Transaction", self, self.dji.Citation)
+        self.sid_trans = DjangoTxn("SID Transaction", self, self.dji.Source)
+        self.oid_trans = DjangoTxn("OID Transaction", self, self.dji.Media)
+        self.rid_trans = DjangoTxn("RID Transaction", self, self.dji.Repository)
+        self.nid_trans = DjangoTxn("NID Transaction", self, self.dji.Note)
+        self.eid_trans = DjangoTxn("EID Transaction", self, self.dji.Event)
+        self.cmap_index = 0
+        self.smap_index = 0
+        self.emap_index = 0
+        self.pmap_index = 0
+        self.fmap_index = 0
+        self.lmap_index = 0
+        self.omap_index = 0
+        self.rmap_index = 0
+        self.nmap_index = 0
+        self.env = Environment(self)
+        self.person_map = Map(Table(self._tables["Person"]))
+        self.family_map = Map(Table(self._tables["Family"]))
+        self.place_map  = Map(Table(self._tables["Place"]))
+        self.citation_map = Map(Table(self._tables["Citation"]))
+        self.source_map = Map(Table(self._tables["Source"]))
+        self.repository_map  = Map(Table(self._tables["Repository"]))
+        self.note_map = Map(Table(self._tables["Note"]))
+        self.media_map  = Map(Table(self._tables["Media"]))
+        self.event_map  = Map(Table(self._tables["Event"]))
+        self.tag_map  = Map(Table(self._tables["Tag"]))
+        self.metadata   = Map(Table({"cursor_func": lambda: MetaCursor()}))
+        self.name_group = {}
+        self.event_names = set()
+        self.individual_attributes = set()
+        self.family_attributes = set()
+        self.source_attributes = set()
+        self.child_ref_types = set()
+        self.family_rel_types = set()
+        self.event_role_names = set()
+        self.name_types = set()
+        self.origin_types = set()
+        self.repository_types = set()
+        self.note_types = set()
+        self.source_media_types = set()
+        self.url_types = set()
+        self.media_attributes = set()
+        self.place_types = set()
+
+    def prepare_import(self):
+        """
+        DbDjango does not commit data on gedcom import, but saves them
+        for later commit.
+        """
+        self.use_import_cache = True
+        self.import_cache = {}
+
+    @transaction.atomic
+    def commit_import(self):
+        """
+        Commits the items that were queued up during the last gedcom
+        import for two step adding.
+        """
+        # First we add the primary objects:
+        for key in list(self.import_cache.keys()):
+            obj = self.import_cache[key]
+            if isinstance(obj, Person):
+                self.dji.add_person(obj.serialize())
+            elif isinstance(obj, Family):
+                self.dji.add_family(obj.serialize())
+            elif isinstance(obj, Event):
+                self.dji.add_event(obj.serialize())
+            elif isinstance(obj, Place):
+                self.dji.add_place(obj.serialize())
+            elif isinstance(obj, Repository):
+                self.dji.add_repository(obj.serialize())
+            elif isinstance(obj, Citation):
+                self.dji.add_citation(obj.serialize())
+            elif isinstance(obj, Source):
+                self.dji.add_source(obj.serialize())
+            elif isinstance(obj, Note):
+                self.dji.add_note(obj.serialize())
+            elif isinstance(obj, MediaObject):
+                self.dji.add_media(obj.serialize())
+            elif isinstance(obj, Tag):
+                self.dji.add_tag(obj.serialize())
+        # Next we add the links:
+        for key in list(self.import_cache.keys()):
+            obj = self.import_cache[key]
+            if isinstance(obj, Person):
+                self.dji.add_person_detail(obj.serialize())
+            elif isinstance(obj, Family):
+                self.dji.add_family_detail(obj.serialize())
+            elif isinstance(obj, Event):
+                self.dji.add_event_detail(obj.serialize())
+            elif isinstance(obj, Place):
+                self.dji.add_place_detail(obj.serialize())
+            elif isinstance(obj, Repository):
+                self.dji.add_repository_detail(obj.serialize())
+            elif isinstance(obj, Citation):
+                self.dji.add_citation_detail(obj.serialize())
+            elif isinstance(obj, Source):
+                self.dji.add_source_detail(obj.serialize())
+            elif isinstance(obj, Note):
+                self.dji.add_note_detail(obj.serialize())
+            elif isinstance(obj, MediaObject):
+                self.dji.add_media_detail(obj.serialize())
+            elif isinstance(obj, Tag):
+                self.dji.add_tag_detail(obj.serialize())
+        self.use_import_cache = False
+        self.import_cache = {}
+        self.dji.update_publics()
+
+    def transaction_commit(self, txn):
+        pass
+
+    def request_rebuild(self):
+        # caches are ok, but let's compute public's
+        self.dji.update_publics()
+        self.emit('person-rebuild')
+        self.emit('family-rebuild')
+        self.emit('place-rebuild')
+        self.emit('source-rebuild')
+        self.emit('citation-rebuild')
+        self.emit('media-rebuild')
+        self.emit('event-rebuild')
+        self.emit('repository-rebuild')
+        self.emit('note-rebuild')
+        self.emit('tag-rebuild')
+
+    def get_undodb(self):
+        return None
+
+    def transaction_abort(self, txn):
+        pass
+
+    @staticmethod
+    def _validated_id_prefix(val, default):
+        if isinstance(val, str) and val:
+            try:
+                str_ = val % 1
+            except TypeError:           # missing conversion specifier
+                prefix_var = val + "%d"
+            except ValueError:          # incomplete format
+                prefix_var = default+"%04d"
+            else:
+                prefix_var = val        # OK as given
+        else:
+            prefix_var = default+"%04d" # not a string or empty string
+        return prefix_var
+
+    @staticmethod
+    def __id2user_format(id_pattern):
+        """
+        Return a method that accepts a Gramps ID and adjusts it to the users
+        format.
+        """
+        pattern_match = re.match(r"(.*)%[0 ](\d+)[diu]$", id_pattern)
+        if pattern_match:
+            str_prefix = pattern_match.group(1)
+            nr_width = pattern_match.group(2)
+            def closure_func(gramps_id):
+                if gramps_id and gramps_id.startswith(str_prefix):
+                    id_number = gramps_id[len(str_prefix):]
+                    if id_number.isdigit():
+                        id_value = int(id_number, 10)
+                        #if len(str(id_value)) > nr_width:
+                        #    # The ID to be imported is too large to fit in the
+                        #    # users format. For now just create a new ID,
+                        #    # because that is also what happens with IDs that
+                        #    # are identical to IDs already in the database. If
+                        #    # the problem of colliding import and already
+                        #    # present IDs is solved the code here also needs
+                        #    # some solution.
+                        #    gramps_id = id_pattern % 1
+                        #else:
+                        gramps_id = id_pattern % id_value
+                return gramps_id
+        else:
+            def closure_func(gramps_id):
+                return gramps_id
+        return closure_func
+
+    def set_person_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Person ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as I%d or I%04d.
+        """
+        self.person_prefix = self._validated_id_prefix(val, "I")
+        self.id2user_format = self.__id2user_format(self.person_prefix)
+
+    def set_citation_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Citation ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as C%d or C%04d.
+        """
+        self.citation_prefix = self._validated_id_prefix(val, "C")
+        self.cid2user_format = self.__id2user_format(self.citation_prefix)
+            
+    def set_source_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Source ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as S%d or S%04d.
+        """
+        self.source_prefix = self._validated_id_prefix(val, "S")
+        self.sid2user_format = self.__id2user_format(self.source_prefix)
+            
+    def set_object_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS MediaObject ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as O%d or O%04d.
+        """
+        self.mediaobject_prefix = self._validated_id_prefix(val, "O")
+        self.oid2user_format = self.__id2user_format(self.mediaobject_prefix)
+
+    def set_place_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Place ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as P%d or P%04d.
+        """
+        self.place_prefix = self._validated_id_prefix(val, "P")
+        self.pid2user_format = self.__id2user_format(self.place_prefix)
+
+    def set_family_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Family ID values. The string is
+        expected to be in the form of a simple text string, or in a format
+        that contains a C/Python style format string using %d, such as F%d
+        or F%04d.
+        """
+        self.family_prefix = self._validated_id_prefix(val, "F")
+        self.fid2user_format = self.__id2user_format(self.family_prefix)
+
+    def set_event_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Event ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as E%d or E%04d.
+        """
+        self.event_prefix = self._validated_id_prefix(val, "E")
+        self.eid2user_format = self.__id2user_format(self.event_prefix)
+
+    def set_repository_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Repository ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as R%d or R%04d.
+        """
+        self.repository_prefix = self._validated_id_prefix(val, "R")
+        self.rid2user_format = self.__id2user_format(self.repository_prefix)
+
+    def set_note_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Note ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as N%d or N%04d.
+        """
+        self.note_prefix = self._validated_id_prefix(val, "N")
+        self.nid2user_format = self.__id2user_format(self.note_prefix)
+
+    def __find_next_gramps_id(self, prefix, map_index, trans):
+        """
+        Helper function for find_next_<object>_gramps_id methods
+        """
+        index = prefix % map_index
+        while trans.get(str(index), txn=self.txn) is not None:
+            map_index += 1
+            index = prefix % map_index
+        map_index += 1
+        return (map_index, index)
+        
+    def find_next_person_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Person object based off the 
+        person ID prefix.
+        """
+        self.pmap_index, gid = self.__find_next_gramps_id(self.person_prefix,
+                                          self.pmap_index, self.id_trans)
+        return gid
+
+    def find_next_place_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Place object based off the 
+        place ID prefix.
+        """
+        self.lmap_index, gid = self.__find_next_gramps_id(self.place_prefix,
+                                          self.lmap_index, self.pid_trans)
+        return gid
+
+    def find_next_event_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Event object based off the 
+        event ID prefix.
+        """
+        self.emap_index, gid = self.__find_next_gramps_id(self.event_prefix,
+                                          self.emap_index, self.eid_trans)
+        return gid
+
+    def find_next_object_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a MediaObject object based
+        off the media object ID prefix.
+        """
+        self.omap_index, gid = self.__find_next_gramps_id(self.mediaobject_prefix,
+                                          self.omap_index, self.oid_trans)
+        return gid
+
+    def find_next_citation_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Citation object based off the 
+        citation ID prefix.
+        """
+        self.cmap_index, gid = self.__find_next_gramps_id(self.citation_prefix,
+                                          self.cmap_index, self.cid_trans)
+        return gid
+
+    def find_next_source_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Source object based off the 
+        source ID prefix.
+        """
+        self.smap_index, gid = self.__find_next_gramps_id(self.source_prefix,
+                                          self.smap_index, self.sid_trans)
+        return gid
+
+    def find_next_family_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Family object based off the 
+        family ID prefix.
+        """
+        self.fmap_index, gid = self.__find_next_gramps_id(self.family_prefix,
+                                          self.fmap_index, self.fid_trans)
+        return gid
+
+    def find_next_repository_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Respository object based 
+        off the repository ID prefix.
+        """
+        self.rmap_index, gid = self.__find_next_gramps_id(self.repository_prefix,
+                                          self.rmap_index, self.rid_trans)
+        return gid
+
+    def find_next_note_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Note object based off the 
+        note ID prefix.
+        """
+        self.nmap_index, gid = self.__find_next_gramps_id(self.note_prefix,
+                                          self.nmap_index, self.nid_trans)
+        return gid
+
+    def get_mediapath(self):
+        return None
+
+    def get_name_group_keys(self):
+        return []
+
+    def get_name_group_mapping(self, key):
+        return None
+
+    def get_researcher(self):
+        obj = Researcher()
+        return obj
+
+    def get_tag_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Tag.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Tag.all()]
+
+    def get_person_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Person.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Person.all()]
+
+    def get_family_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Family.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Family.all()]
+
+    def get_event_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Event.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Event.all()]
+
+    def get_citation_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Citation.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Citation.all()]
+
+    def get_source_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Source.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Source.all()]
+
+    def get_place_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Place.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Place.all()]
+
+    def get_repository_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Repository.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Repository.all()]
+
+    def get_media_object_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Media.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Media.all()]
+
+    def get_note_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Note.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Note.all()]
+
+    def get_media_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            media = self.dji.Media.get(handle=handle)
+        except:
+            return None
+        return self.make_media(media)
+
+    def get_event_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            event = self.dji.Event.get(handle=handle)
+        except:
+            return None
+        return self.make_event(event)
+
+    def get_family_from_handle(self, handle): 
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            family = self.dji.Family.get(handle=handle)
+        except:
+            return None
+        return self.make_family(family)
+
+    def get_repository_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            repository = self.dji.Repository.get(handle=handle)
+        except:
+            return None
+        return self.make_repository(repository)
+
+    def get_person_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            person = self.dji.Person.get(handle=handle)
+        except:
+            return None
+        return self.make_person(person)
+
+    def get_tag_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            tag = self.dji.Tag.get(handle=handle)
+        except:
+            return None
+        return self.make_tag(tag)
+
+    def make_repository(self, repository):
+        if self.use_db_cache and repository.cache:
+            data = repository.from_cache()
+        else:
+            data = self.dji.get_repository(repository)
+        return Repository.create(data)
+
+    def make_citation(self, citation):
+        if self.use_db_cache and citation.cache:
+            data = citation.from_cache()
+        else:
+            data = self.dji.get_citation(citation)
+        return Citation.create(data)
+
+    def make_source(self, source):
+        if self.use_db_cache and source.cache:
+            data = source.from_cache()
+        else:
+            data = self.dji.get_source(source)
+        return Source.create(data)
+
+    def make_family(self, family):
+        if self.use_db_cache and family.cache:
+            data = family.from_cache()
+        else:
+            data = self.dji.get_family(family)
+        return Family.create(data)
+
+    def make_person(self, person):
+        if self.use_db_cache and person.cache:
+            data = person.from_cache()
+        else:
+            data = self.dji.get_person(person)
+        return Person.create(data)
+
+    def make_event(self, event):
+        if self.use_db_cache and event.cache:
+            data = event.from_cache()
+        else:
+            data = self.dji.get_event(event)
+        return Event.create(data)
+
+    def make_note(self, note):
+        if self.use_db_cache and note.cache:
+            data = note.from_cache()
+        else:
+            data = self.dji.get_note(note)
+        return Note.create(data)
+
+    def make_tag(self, tag):
+        data = self.dji.get_tag(tag)
+        return Tag.create(data)
+
+    def make_place(self, place):
+        if self.use_db_cache and place.cache:
+            data = place.from_cache()
+        else:
+            data = self.dji.get_place(place)
+        return Place.create(data)
+
+    def make_media(self, media):
+        if self.use_db_cache and media.cache:
+            data = media.from_cache()
+        else:
+            data = self.dji.get_media(media)
+        return MediaObject.create(data)
+
+    def get_place_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            place = self.dji.Place.get(handle=handle)
+        except:
+            return None
+        return self.make_place(place)
+
+    def get_citation_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            citation = self.dji.Citation.get(handle=handle)
+        except:
+            return None
+        return self.make_citation(citation)
+
+    def get_source_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            source = self.dji.Source.get(handle=handle)
+        except:
+            return None
+        return self.make_source(source)
+
+    def get_note_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            note = self.dji.Note.get(handle=handle)
+        except:
+            return None
+        return self.make_note(note)
+
+    def get_object_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            media = self.dji.Media.get(handle=handle)
+        except:
+            return None
+        return self.make_media(media)
+
+    def get_default_person(self):
+        people = self.dji.Person.all()
+        if people.count() > 0:
+            return self.make_person(people[0])
+        return None
+
+    def iter_people(self):
+        return (self.get_person_from_handle(person.handle) 
+                for person in self.dji.Person.all())
+
+    def iter_person_handles(self):
+        return (person.handle for person in self.dji.Person.all())
+
+    def iter_families(self):
+        return (self.get_family_from_handle(family.handle) 
+                for family in self.dji.Family.all())
+
+    def iter_family_handles(self):
+        return (family.handle for family in self.dji.Family.all())
+
+    def iter_notes(self):
+        return (self.get_note_from_handle(note.handle) 
+                for note in self.dji.Note.all())
+
+    def iter_note_handles(self):
+        return (note.handle for note in self.dji.Note.all())
+
+    def iter_events(self):
+        return (self.get_event_from_handle(event.handle) 
+                for event in self.dji.Event.all())
+
+    def iter_event_handles(self):
+        return (event.handle for event in self.dji.Event.all())
+
+    def iter_places(self):
+        return (self.get_place_from_handle(place.handle) 
+                for place in self.dji.Place.all())
+
+    def iter_place_handles(self):
+        return (place.handle for place in self.dji.Place.all())
+
+    def iter_repositories(self):
+        return (self.get_repository_from_handle(repository.handle) 
+                for repository in self.dji.Repository.all())
+
+    def iter_repository_handles(self):
+        return (repository.handle for repository in self.dji.Repository.all())
+
+    def iter_sources(self):
+        return (self.get_source_from_handle(source.handle) 
+                for source in self.dji.Source.all())
+
+    def iter_source_handles(self):
+        return (source.handle for source in self.dji.Source.all())
+
+    def iter_citations(self):
+        return (self.get_citation_from_handle(citation.handle) 
+                for citation in self.dji.Citation.all())
+
+    def iter_citation_handles(self):
+        return (citation.handle for citation in self.dji.Citation.all())
+
+    def iter_tags(self):
+        return (self.get_tag_from_handle(tag.handle) 
+                for tag in self.dji.Tag.all())
+
+    def iter_tag_handles(self):
+        return (tag.handle for tag in self.dji.Tag.all())
+
+    def iter_media_objects(self):
+        return (self.get_media_from_handle(media.handle) 
+                for media in self.dji.Media.all())
+
+    def get_tag_from_name(self, name):
+        try:
+            tag = self.dji.Tag.filter(name=name)
+            return self.make_tag(tag[0])
+        except:
+            return None
+
+    def get_person_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Person.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_person(match_list[0])
+        else:
+            return None
+
+    def get_family_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        try:
+            family = self.dji.Family.get(gramps_id=gramps_id)
+        except:
+            return None
+        return self.make_family(family)
+
+    def get_source_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Source.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_source(match_list[0])
+        else:
+            return None
+
+    def get_citation_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Citation.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_citation(match_list[0])
+        else:
+            return None
+
+    def get_event_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Event.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_event(match_list[0])
+        else:
+            return None
+
+    def get_object_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Media.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_media(match_list[0])
+        else:
+            return None
+
+    def get_place_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Place.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_place(match_list[0])
+        else:
+            return None
+
+    def get_repository_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Repsoitory.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_repository(match_list[0])
+        else:
+            return None
+
+    def get_note_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Note.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_note(match_list[0])
+        else:
+            return None
+
+    def get_number_of_people(self):
+        return self.dji.Person.count()
+
+    def get_number_of_events(self):
+        return self.dji.Event.count()
+
+    def get_number_of_places(self):
+        return self.dji.Place.count()
+
+    def get_number_of_tags(self):
+        return self.dji.Tag.count()
+
+    def get_number_of_families(self):
+        return self.dji.Family.count()
+
+    def get_number_of_notes(self):
+        return self.dji.Note.count()
+
+    def get_number_of_citations(self):
+        return self.dji.Citation.count()
+
+    def get_number_of_sources(self):
+        return self.dji.Source.count()
+
+    def get_number_of_media_objects(self):
+        return self.dji.Media.count()
+
+    def get_number_of_repositories(self):
+        return self.dji.Repository.count()
+
+    def get_place_cursor(self):
+        return Cursor(self.dji.Place, self.get_raw_place_data)
+
+    def get_person_cursor(self):
+        return Cursor(self.dji.Person, self.get_raw_person_data)
+
+    def get_family_cursor(self):
+        return Cursor(self.dji.Family, self.get_raw_family_data)
+
+    def get_event_cursor(self):
+        return Cursor(self.dji.Event, self.get_raw_event_data)
+
+    def get_citation_cursor(self):
+        return Cursor(self.dji.Citation, self.get_raw_citation_data)
+
+    def get_source_cursor(self):
+        return Cursor(self.dji.Source, self.get_raw_source_data)
+
+    def get_note_cursor(self):
+        return Cursor(self.dji.Note, self.get_raw_note_data)
+
+    def get_tag_cursor(self):
+        return Cursor(self.dji.Tag, self.get_raw_tag_data)
+
+    def get_repository_cursor(self):
+        return Cursor(self.dji.Repository, self.get_raw_repository_data)
+
+    def get_media_cursor(self):
+        return Cursor(self.dji.Media, self.get_raw_object_data)
+
+    def has_gramps_id(self, obj_key, gramps_id):
+        key2table = {
+            PERSON_KEY:     self.dji.Person, 
+            FAMILY_KEY:     self.dji.Family, 
+            SOURCE_KEY:     self.dji.Source, 
+            CITATION_KEY:   self.dji.Citation, 
+            EVENT_KEY:      self.dji.Event, 
+            MEDIA_KEY:      self.dji.Media, 
+            PLACE_KEY:      self.dji.Place, 
+            REPOSITORY_KEY: self.dji.Repository, 
+            NOTE_KEY:       self.dji.Note, 
+            }
+        table = key2table[obj_key]
+        return table.filter(gramps_id=gramps_id).count() > 0
+
+    def has_person_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Person.filter(handle=handle).count() == 1
+
+    def has_family_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Family.filter(handle=handle).count() == 1
+
+    def has_citation_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Citation.filter(handle=handle).count() == 1
+
+    def has_source_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Source.filter(handle=handle).count() == 1
+
+    def has_repository_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Repository.filter(handle=handle).count() == 1
+
+    def has_note_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Note.filter(handle=handle).count() == 1
+
+    def has_place_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Place.filter(handle=handle).count() == 1
+
+    def has_event_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Event.filter(handle=handle).count() == 1
+
+    def has_tag_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Tag.filter(handle=handle).count() == 1
+
+    def has_object_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Media.filter(handle=handle).count() == 1
+
+    def has_name_group_key(self, key):
+        # FIXME:
+        return False
+
+    def set_name_group_mapping(self, key, value):
+        # FIXME:
+        pass
+
+    def set_default_person_handle(self, handle):
+        pass
+
+    def set_mediapath(self, mediapath):
+        pass
+
+    def get_raw_person_data(self, handle):
+        try:
+            return self.dji.get_person(self.dji.Person.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_family_data(self, handle):
+        try:
+            return self.dji.get_family(self.dji.Family.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_citation_data(self, handle):
+        try:
+            return self.dji.get_citation(self.dji.Citation.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_source_data(self, handle):
+        try:
+            return self.dji.get_source(self.dji.Source.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_repository_data(self, handle):
+        try:
+            return self.dji.get_repository(self.dji.Repository.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_note_data(self, handle):
+        try:
+            return self.dji.get_note(self.dji.Note.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_place_data(self, handle):
+        try:
+            return self.dji.get_place(self.dji.Place.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_object_data(self, handle):
+        try:
+            return self.dji.get_media(self.dji.Media.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_tag_data(self, handle):
+        try:
+            return self.dji.get_tag(self.dji.Tag.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_event_data(self, handle):
+        try:
+            return self.dji.get_event(self.dji.Event.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def add_person(self, person, trans, set_gid=True):
+        if not person.handle:
+            person.handle = create_id()
+        if not person.gramps_id and set_gid:
+            person.gramps_id = self.find_next_person_gramps_id()
+        self.commit_person(person, trans)
+        self.emit("person-add", ([person.handle],))
+        return person.handle
+
+    def add_family(self, family, trans, set_gid=True):
+        if not family.handle:
+            family.handle = create_id()
+        if not family.gramps_id and set_gid:
+            family.gramps_id = self.find_next_family_gramps_id()
+        self.commit_family(family, trans)
+        self.emit("family-add", ([family.handle],))
+        return family.handle
+
+    def add_citation(self, citation, trans, set_gid=True):
+        if not citation.handle:
+            citation.handle = create_id()
+        if not citation.gramps_id and set_gid:
+            citation.gramps_id = self.find_next_citation_gramps_id()
+        self.commit_citation(citation, trans)
+        self.emit("citation-add", ([citation.handle],))
+        return citation.handle
+
+    def add_source(self, source, trans, set_gid=True):
+        if not source.handle:
+            source.handle = create_id()
+        if not source.gramps_id and set_gid:
+            source.gramps_id = self.find_next_source_gramps_id()
+        self.commit_source(source, trans)
+        self.emit("source-add", ([source.handle],))
+        return source.handle
+
+    def add_repository(self, repository, trans, set_gid=True):
+        if not repository.handle:
+            repository.handle = create_id()
+        if not repository.gramps_id and set_gid:
+            repository.gramps_id = self.find_next_repository_gramps_id()
+        self.commit_repository(repository, trans)
+        self.emit("repository-add", ([repository.handle],))
+        return repository.handle
+
+    def add_note(self, note, trans, set_gid=True):
+        if not note.handle:
+            note.handle = create_id()
+        if not note.gramps_id and set_gid:
+            note.gramps_id = self.find_next_note_gramps_id()
+        self.commit_note(note, trans)
+        self.emit("note-add", ([note.handle],))
+        return note.handle
+
+    def add_place(self, place, trans, set_gid=True):
+        if not place.handle:
+            place.handle = create_id()
+        if not place.gramps_id and set_gid:
+            place.gramps_id = self.find_next_place_gramps_id()
+        self.commit_place(place, trans)
+        return place.handle
+
+    def add_event(self, event, trans, set_gid=True):
+        if not event.handle:
+            event.handle = create_id()
+        if not event.gramps_id and set_gid:
+            event.gramps_id = self.find_next_event_gramps_id()
+        self.commit_event(event, trans)
+        return event.handle
+
+    def add_tag(self, tag, trans):
+        if not tag.handle:
+            tag.handle = create_id()
+        self.commit_event(tag, trans)
+        return tag.handle
+
+    def add_object(self, obj, transaction, set_gid=True):
+        """
+        Add a MediaObject to the database, assigning internal IDs if they have
+        not already been defined.
+        
+        If not set_gid, then gramps_id is not set.
+        """
+        if not obj.handle:
+            obj.handle = create_id()
+        if not obj.gramps_id and set_gid:
+            obj.gramps_id = self.find_next_object_gramps_id()
+        self.commit_media_object(obj, transaction)
+        return obj.handle
+
+    def commit_person(self, person, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[person.handle] = person
+        else:
+            raw = person.serialize()
+            items = self.dji.Person.filter(handle=person.handle)
+            if items.count() > 0:
+                # Hack, for the moment: delete and re-add
+                items[0].delete()
+            self.dji.add_person(person.serialize())
+            self.dji.add_person_detail(person.serialize())
+            if items.count() > 0:
+                self.emit("person-update", ([person.handle],))
+            else:
+                self.emit("person-add", ([person.handle],))
+
+    def commit_family(self, family, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[family.handle] = family
+        else:
+            raw = family.serialize()
+            items = self.dji.Family.filter(handle=family.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_family(family.serialize())
+            self.dji.add_family_detail(family.serialize())
+            if items.count() > 0:
+                self.emit("family-update", ([family.handle],))
+            else:
+                self.emit("family-add", ([family.handle],))
+
+    def commit_citation(self, citation, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[citation.handle] = citation
+        else:
+            raw = citation.serialize()
+            items = self.dji.Citation.filter(handle=citation.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_citation(citation.serialize())
+            self.dji.add_citation_detail(citation.serialize())
+            if items.count() > 0:
+                self.emit("citation-update", ([citation.handle],))
+            else:
+                self.emit("citation-add", ([citation.handle],))
+
+    def commit_source(self, source, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[source.handle] = source
+        else:
+            raw = source.serialize()
+            items = self.dji.Source.filter(handle=source.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_source(source.serialize())
+            self.dji.add_source_detail(source.serialize())
+            if items.count() > 0:
+                self.emit("source-update", ([source.handle],))
+            else:
+                self.emit("source-add", ([source.handle],))
+
+    def commit_repository(self, repository, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[repository.handle] = repository
+        else:
+            raw = repository.serialize()
+            items = self.dji.Repository.filter(handle=repository.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_repository(repository.serialize())
+            self.dji.add_repository_detail(repository.serialize())
+            if items.count() > 0:
+                self.emit("repository-update", ([repository.handle],))
+            else:
+                self.emit("repository-add", ([repository.handle],))
+
+    def commit_note(self, note, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[note.handle] = note
+        else:
+            raw = note.serialize()
+            items = self.dji.Note.filter(handle=note.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_note(note.serialize())
+            self.dji.add_note_detail(note.serialize())
+            if items.count() > 0:
+                self.emit("note-update", ([note.handle],))
+            else:
+                self.emit("note-add", ([note.handle],))
+
+    def commit_place(self, place, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[place.handle] = place
+        else:
+            raw = place.serialize()
+            items = self.dji.Place.filter(handle=place.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_place(place.serialize())
+            self.dji.add_place_detail(place.serialize())
+            if items.count() > 0:
+                self.emit("place-update", ([place.handle],))
+            else:
+                self.emit("place-add", ([place.handle],))
+
+    def commit_event(self, event, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[event.handle] = event
+        else:
+            raw = event.serialize()
+            items = self.dji.Event.filter(handle=event.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_event(event.serialize())
+            self.dji.add_event_detail(event.serialize())
+            if items.count() > 0:
+                self.emit("event-update", ([event.handle],))
+            else:
+                self.emit("event-add", ([event.handle],))
+
+    def commit_tag(self, tag, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[tag.handle] = tag
+        else:
+            raw = tag.serialize()
+            items = self.dji.Tag.filter(handle=tag.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_tag(tag.serialize())
+            self.dji.add_tag_detail(tag.serialize())
+            if items.count() > 0:
+                self.emit("tag-update", ([tag.handle],))
+            else:
+                self.emit("tag-add", ([tag.handle],))
+
+    def commit_media_object(self, media, transaction, change_time=None):
+        """
+        Commit the specified MediaObject to the database, storing the changes
+        as part of the transaction.
+        """
+        if self.use_import_cache:
+            self.import_cache[obj.handle] = media
+        else:
+            raw = media.serialize()
+            items = self.dji.Media.filter(handle=media.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_media(media.serialize())
+            self.dji.add_media_detail(media.serialize())
+            if items.count() > 0:
+                self.emit("media-update", ([media.handle],))
+            else:
+                self.emit("media-add", ([media.handle],))
+
+    def get_gramps_ids(self, obj_key):
+        key2table = {
+            PERSON_KEY:     self.id_trans, 
+            FAMILY_KEY:     self.fid_trans, 
+            CITATION_KEY:   self.cid_trans, 
+            SOURCE_KEY:     self.sid_trans, 
+            EVENT_KEY:      self.eid_trans, 
+            MEDIA_KEY:      self.oid_trans, 
+            PLACE_KEY:      self.pid_trans, 
+            REPOSITORY_KEY: self.rid_trans, 
+            NOTE_KEY:       self.nid_trans, 
+            }
+
+        table = key2table[obj_key]
+        return list(table.keys())
+
+    def transaction_begin(self, transaction):
+        return 
+
+    def set_researcher(self, owner):
+        pass
+
+    def copy_from_db(self, db):
+        """
+        A (possibily) implementation-specific method to get data from
+        db into this database.
+        """
+        # First we add the primary objects:
+        for key in db._tables.keys():
+            cursor = db._tables[key]["cursor_func"]
+            for (handle, data) in cursor():
+                if key == "Person":
+                    self.dji.add_person(data)
+                elif key == "Family":
+                    self.dji.add_family(data)
+                elif key == "Event":
+                    self.dji.add_event(data)
+                elif key == "Place":
+                    self.dji.add_place(data)
+                elif key == "Repository":
+                    self.dji.add_repository(data)
+                elif key == "Citation":
+                    self.dji.add_citation(data)
+                elif key == "Source":
+                    self.dji.add_source(data)
+                elif key == "Note":
+                    self.dji.add_note(data)
+                elif key == "Media":
+                    self.dji.add_media(data)
+                elif key == "Tag":
+                    self.dji.add_tag(data)
+        for key in db._tables.keys():
+            cursor = db._tables[key]["cursor_func"]
+            for (handle, data) in cursor():
+                if key == "Person":
+                    self.dji.add_person_detail(data)
+                elif key == "Family":
+                    self.dji.add_family_detail(data)
+                elif key == "Event":
+                    self.dji.add_event_detail(data)
+                elif key == "Place":
+                    self.dji.add_place_detail(data)
+                elif key == "Repository":
+                    self.dji.add_repository_detail(data)
+                elif key == "Citation":
+                    self.dji.add_citation_detail(data)
+                elif key == "Source":
+                    self.dji.add_source_detail(data)
+                elif key == "Note":
+                    self.dji.add_note_detail(data)
+                elif key == "Media":
+                    self.dji.add_media_detail(data)
+                elif key == "Tag":
+                    self.dji.add_tag_detail(data)
+            # Next we add the links:
+        self.dji.update_publics()
+
+    def get_from_name_and_handle(self, table_name, handle):
+        """
+        Returns a gen.lib object (or None) given table_name and
+        handle.
+
+        Examples:
+
+        >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008")
+        >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23")
+        """
+        if table_name in self._tables:
+            return self._tables[table_name]["handle_func"](handle)
+        return None
+
+    def is_empty(self):
+        """
+        Is the database empty?
+        """
+        return (self.get_number_of_people() == 0 and
+                self.get_number_of_events() == 0 and
+                self.get_number_of_places() == 0 and
+                self.get_number_of_tags() == 0 and
+                self.get_number_of_families() == 0 and
+                self.get_number_of_notes() == 0 and
+                self.get_number_of_citations() == 0 and
+                self.get_number_of_sources() == 0 and
+                self.get_number_of_media_objects() == 0 and
+                self.get_number_of_repositories() == 0)
+                
+    __callback_map = {}
+
+    def set_prefixes(self, person, media, family, source, citation,
+                     place, event, repository, note):
+        self.set_person_id_prefix(person)
+        self.set_object_id_prefix(media)
+        self.set_family_id_prefix(family)
+        self.set_source_id_prefix(source)
+        self.set_citation_id_prefix(citation)
+        self.set_place_id_prefix(place)
+        self.set_event_id_prefix(event)
+        self.set_repository_id_prefix(repository)
+        self.set_note_id_prefix(note)
+
+    def has_changed(self):
+        return False
+
+    def find_backlink_handles(self, handle, include_classes=None):
+        ## FIXME: figure out how to get objects that refer
+        ## to this handle
+        return []
+
+    def get_note_bookmarks(self):
+        return self.note_bookmarks
+
+    def get_media_bookmarks(self):
+        return self.media_bookmarks
+
+    def get_repo_bookmarks(self):
+        return self.repo_bookmarks
+
+    def get_citation_bookmarks(self):
+        return self.citation_bookmarks
+
+    def get_source_bookmarks(self):
+        return self.source_bookmarks
+
+    def get_place_bookmarks(self):
+        return self.place_bookmarks
+
+    def get_event_bookmarks(self):
+        return self.event_bookmarks
+
+    def get_bookmarks(self):
+        return self.bookmarks
+
+    def get_family_bookmarks(self):
+        return self.family_bookmarks
+
+    def get_save_path(self):
+        return "/tmp/"
+
+    ## Get types:
+    def get_event_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Event instances
+        in the database.
+        """
+        return list(self.event_attributes)
+
+    def get_event_types(self):
+        """
+        Return a list of all event types in the database.
+        """
+        return list(self.event_names)
+
+    def get_person_event_types(self):
+        """
+        Deprecated:  Use get_event_types
+        """
+        return list(self.event_names)
+
+    def get_person_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Person instances 
+        in the database.
+        """
+        return list(self.individual_attributes)
+
+    def get_family_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Family instances 
+        in the database.
+        """
+        return list(self.family_attributes)
+
+    def get_family_event_types(self):
+        """
+        Deprecated:  Use get_event_types
+        """
+        return list(self.event_names)
+
+    def get_media_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Media and MediaRef 
+        instances in the database.
+        """
+        return list(self.media_attributes)
+
+    def get_family_relation_types(self):
+        """
+        Return a list of all relationship types assocated with Family
+        instances in the database.
+        """
+        return list(self.family_rel_types)
+
+    def get_child_reference_types(self):
+        """
+        Return a list of all child reference types assocated with Family
+        instances in the database.
+        """
+        return list(self.child_ref_types)
+
+    def get_event_roles(self):
+        """
+        Return a list of all custom event role names assocated with Event
+        instances in the database.
+        """
+        return list(self.event_role_names)
+
+    def get_name_types(self):
+        """
+        Return a list of all custom names types assocated with Person
+        instances in the database.
+        """
+        return list(self.name_types)
+
+    def get_origin_types(self):
+        """
+        Return a list of all custom origin types assocated with Person/Surname
+        instances in the database.
+        """
+        return list(self.origin_types)
+
+    def get_repository_types(self):
+        """
+        Return a list of all custom repository types assocated with Repository 
+        instances in the database.
+        """
+        return list(self.repository_types)
+
+    def get_note_types(self):
+        """
+        Return a list of all custom note types assocated with Note instances 
+        in the database.
+        """
+        return list(self.note_types)
+
+    def get_source_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Source/Citation
+        instances in the database.
+        """
+        return list(self.source_attributes)
+
+    def get_source_media_types(self):
+        """
+        Return a list of all custom source media types assocated with Source 
+        instances in the database.
+        """
+        return list(self.source_media_types)
+
+    def get_url_types(self):
+        """
+        Return a list of all custom names types assocated with Url instances 
+        in the database.
+        """
+        return list(self.url_types)
+
+    def get_place_types(self):
+        """
+        Return a list of all custom place types assocated with Place instances
+        in the database.
+        """
+        return list(self.place_types)
+
+    def get_default_handle(self):
+        people = self.dji.Person.all()
+        if people.count() > 0:
+            return people[0].handle
+        return None
+
+    def close(self):
+        pass
+
+    def get_surname_list(self):
+        return []
+
+    def is_open(self):
+        return True
+
+    def get_table_names(self):
+        """Return a list of valid table names."""
+        return list(self._tables.keys())
+
+    def find_initial_person(self):
+        return self.get_default_person()
+
+    # Removals:
+    def remove_person(self, handle, txn):
+        self.dji.Person.filter(handle=handle)[0].delete()
+        self.emit("person-delete", ([handle],))
+
+    def remove_source(self, handle, transaction):
+        self.dji.Source.filter(handle=handle)[0].delete()
+        self.emit("source-delete", ([handle],))
+
+    def remove_citation(self, handle, transaction):
+        self.dji.Citation.filter(handle=handle)[0].delete()
+        self.emit("citation-delete", ([handle],))
+
+    def remove_event(self, handle, transaction):
+        self.dji.Event.filter(handle=handle)[0].delete()
+        self.emit("event-delete", ([handle],))
+
+    def remove_object(self, handle, transaction):
+        self.dji.Media.filter(handle=handle)[0].delete()
+        self.emit("media-delete", ([handle],))
+
+    def remove_place(self, handle, transaction):
+        self.dji.Place.filter(handle=handle)[0].delete()
+        self.emit("place-delete", ([handle],))
+
+    def remove_family(self, handle, transaction):
+        self.dji.Family.filter(handle=handle)[0].delete()
+        self.emit("family-delete", ([handle],))
+
+    def remove_repository(self, handle, transaction):
+        self.dji.Repository.filter(handle=handle)[0].delete()
+        self.emit("repository-delete", ([handle],))
+
+    def remove_note(self, handle, transaction):
+        self.dji.Note.filter(handle=handle)[0].delete()
+        self.emit("note-delete", ([handle],))
+
+    def remove_tag(self, handle, transaction):
+        self.dji.Tag.filter(handle=handle)[0].delete()
+        self.emit("tag-delete", ([handle],))
+
+    def remove_from_surname_list(self, person):
+        ## FIXME
+        pass
+    
+    def get_dbname(self):
+        return "Django Database"
+
+    ## missing
+
+    def find_place_child_handles(self, handle):
+        pass
+
+    def get_cursor(self, table, txn=None, update=False, commit=False):
+        pass
+
+    def get_from_name_and_handle(self, table_name, handle):
+        """
+        Returns a gen.lib object (or None) given table_name and
+        handle.
+
+        Examples:
+
+        >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008")
+        >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23")
+        """
+        if table_name in self._tables:
+            return self._tables[table_name]["handle_func"](handle)
+        return None
+
+    def get_number_of_records(self, table):
+        pass
+
+    def get_place_parent_cursor(self):
+        pass
+
+    def get_place_tree_cursor(self):
+        pass
+
+    def get_table_metadata(self, table_name):
+        """Return the metadata for a valid table name."""
+        if table_name in self._tables:
+            return self._tables[table_name]
+        return None
+
+    def get_transaction_class(self):
+        pass
+
+    def undo(self, update_history=True):
+        # FIXME:
+        return self.undodb.undo(update_history)
+
+    def redo(self, update_history=True):
+        # FIXME:
+        return self.undodb.redo(update_history)
+
+    def backup(self):
+        pass
+
+    def restore(self):
+        pass

From 6bff90419e99d814886b7a9f95e1450c93534596 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Wed, 13 May 2015 12:36:17 -0400
Subject: [PATCH 006/105] Loads tree based on id in database.txt

---
 gramps/cli/grampscli.py             | 10 +++++++++-
 gramps/gui/dbloader.py              | 10 +++++++++-
 gramps/plugins/database/djangodb.py | 19 ++++++++++++++++---
 3 files changed, 34 insertions(+), 5 deletions(-)

diff --git a/gramps/cli/grampscli.py b/gramps/cli/grampscli.py
index d1d3cfc07..d7b9afea6 100644
--- a/gramps/cli/grampscli.py
+++ b/gramps/cli/grampscli.py
@@ -47,6 +47,7 @@ LOG = logging.getLogger(".grampscli")
 from gramps.gen.display.name import displayer as name_displayer
 from gramps.gen.config import config
 from gramps.gen.const import PLUGINS_DIR, USER_PLUGINS
+from gramps.gen.db.dbconst import DBBACKEND
 from gramps.gen.errors import DbError
 from gramps.gen.dbstate import DbState
 from gramps.gen.db.exceptions import (DbUpgradeRequiredError, 
@@ -151,7 +152,14 @@ class CLIDbLoader(object):
         else:
             mode = 'w'
 
-        db = self.dbstate.make_database("bsddb")
+        dbid_path = os.path.join(filename, DBBACKEND)
+        if os.path.isfile(dbid_path):
+            with open(dbid_path) as fp:
+                dbid = fp.read().strip()
+        else:
+            dbid = "bsddb"
+
+        db = self.dbstate.make_database(dbid)
         
         self.dbstate.change_database(db)
         self.dbstate.db.disable_signals()
diff --git a/gramps/gui/dbloader.py b/gramps/gui/dbloader.py
index a0105d9a3..863aea669 100644
--- a/gramps/gui/dbloader.py
+++ b/gramps/gui/dbloader.py
@@ -52,6 +52,7 @@ from gi.repository import GObject
 #
 #-------------------------------------------------------------------------
 from gramps.gen.const import GRAMPS_LOCALE as glocale
+from gramps.gen.db.dbconst import DBBACKEND
 _ = glocale.translation.gettext
 from gramps.cli.grampscli import CLIDbLoader
 from gramps.gen.config import config
@@ -304,7 +305,14 @@ class DbLoader(CLIDbLoader):
         else:
             mode = 'w'
 
-        db = self.dbstate.make_database("bsddb")
+        dbid_path = os.path.join(filename, DBBACKEND)
+        if os.path.isfile(dbid_path):
+            with open(dbid_path) as fp:
+                dbid = fp.read().strip()
+        else:
+            dbid = "bsddb"
+
+        db = self.dbstate.make_database(dbid)
         db.disable_signals()
         self.dbstate.no_database()
 
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index adb28dc54..e0d8c043a 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -55,6 +55,11 @@ from gramps.gen.db import (PERSON_KEY,
 from gramps.gen.utils.id import create_id
 from django.db import transaction
 
+## add this directory to sys path, so we can find django_support later:
+import sys
+import os
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+
 class Environment(object):
     """
     Implements the Environment API.
@@ -322,10 +327,15 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if directory:
             self.load(directory)
 
-    def load(self, directory, pulse_progress=None, mode=None):
+    def load(self, directory, pulse_progress=None, mode=None, 
+             force_schema_upgrade=False,
+             force_bsddb_upgrade=False,
+             force_bsddb_downgrade=False,
+             force_python_upgrade=False):
         self._directory = directory
         from django.conf import settings
-        default_settings = {}
+        default_settings = {"__file__": 
+                            os.path.join(directory, "default_settings.py")}
         settings_file = os.path.join(directory, "default_settings.py")
         with open(settings_file) as f:
             code = compile(f.read(), settings_file, 'exec')
@@ -1785,7 +1795,10 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return self.family_bookmarks
 
     def get_save_path(self):
-        return "/tmp/"
+        return self._directory
+
+    def set_save_path(self, directory):
+        self._directory = directory
 
     ## Get types:
     def get_event_attribute_types(self):

From fbbd9d9c6e2d912cea54563868181468d23cefdd Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Wed, 13 May 2015 19:26:14 -0400
Subject: [PATCH 007/105] DictionaryDb: adding missing functions, bringing up
 to date

---
 gramps/plugins/database/dictionarydb.py | 454 ++++++++++++++++++++----
 gramps/plugins/database/djangodb.py     |   6 +-
 gramps/webapp/utils.py                  |   6 +-
 3 files changed, 395 insertions(+), 71 deletions(-)

diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index a3dbd9586..621425e2d 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -29,6 +29,8 @@ import base64
 import time
 import re
 from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn
+from gramps.gen.utils.callback import Callback
+from gramps.gen.updatecallback import UpdateCallback
 from gramps.gen.db import (PERSON_KEY,
                            FAMILY_KEY,
                            CITATION_KEY,
@@ -53,33 +55,101 @@ from gramps.gen.lib.repo import Repository
 from gramps.gen.lib.note import Note
 from gramps.gen.lib.tag import Tag
 
+class Environment(object):
+    """
+    Implements the Environment API.
+    """
+    def __init__(self, db):
+        self.db = db
+
+    def txn_begin(self):
+        return DictionaryTxn("DictionaryDb Transaction", self.db)
+
+class Table(object):
+    """
+    Implements Table interface.
+    """
+    def __init__(self, funcs):
+        self.funcs = funcs
+
+    def cursor(self):
+        """
+        Returns a Cursor for this Table.
+        """
+        return self.funcs["cursor_func"]()
+
+    def put(key, data, txn=None):
+        self[key] = data
+
+class Map(dict):
+    """
+    Implements the map API for person_map, etc.
+    
+    Takes a Table() as argument.
+    """
+    def __init__(self, tbl, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.db = tbl
+
+class MetaCursor(object):
+    def __init__(self):
+        pass
+    def __enter__(self):
+        return self
+    def __iter__(self):
+        return self.__next__()
+    def __next__(self):
+        yield None
+    def __exit__(self, *args, **kwargs):
+        pass
+    def iter(self):
+        yield None
+    def first(self):
+        self._iter = self.__iter__()
+        return self.next()
+    def next(self):
+        try:
+            return next(self._iter)
+        except:
+            return None
+    def close(self):
+        pass
+
 class Cursor(object):
-    """
-    Iterates through model returning (handle, raw_data)...
-    """
-    def __init__(self, model, func):
-        self.model = model
+    def __init__(self, map, func):
+        self.map = map
         self.func = func
     def __enter__(self):
         return self
     def __iter__(self):
-        return self
+        return self.__next__()
     def __next__(self):
-        for handle in self.model.keys():
-            return (handle, self.func(handle))
-    def next(self):
-        for handle in self.model.keys():
-            return (handle, self.func(handle))
+        for item in self.map.keys():
+            yield (bytes(item, "utf-8"), self.func(item))
     def __exit__(self, *args, **kwargs):
         pass
+    def iter(self):
+        for item in self.map.keys():
+            yield (bytes(item, "utf-8"), self.func(item))
+        yield None
+    def first(self):
+        self._iter = self.__iter__()
+        return self.next()
+    def next(self):
+        try:
+            return next(self._iter)
+        except:
+            return None
     def close(self):
         pass
 
-class Bookmarks:
+class Bookmarks(object):
+    def __init__(self):
+        self.handles = []
     def get(self):
-        return [] # handles
+        return self.handles
     def append(self, handle):
-        pass
+        self.handles.append(handle)
 
 class DictionaryTxn(DbTxn):
     def __init__(self, message, db, batch=False):
@@ -99,13 +169,38 @@ class DictionaryTxn(DbTxn):
         """
         txn[handle] = new_data
 
-class DictionaryDb(DbWriteBase, DbReadBase):
+class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     """
     A Gramps Database Backend. This replicates the grampsdb functions.
     """
+    __signals__ = dict((obj+'-'+op, signal)
+                       for obj in
+                       ['person', 'family', 'event', 'place',
+                        'source', 'citation', 'media', 'note', 'repository', 'tag']
+                       for op, signal in zip(
+                               ['add',   'update', 'delete', 'rebuild'],
+                               [(list,), (list,),  (list,),   None]
+                       )
+                   )
+    
+    # 2. Signals for long operations
+    __signals__.update(('long-op-'+op, signal) for op, signal in zip(
+        ['start',  'heartbeat', 'end'],
+        [(object,), None,       None]
+        ))
+
+    # 3. Special signal for change in home person
+    __signals__['home-person-changed'] = None
+
+    # 4. Signal for change in person group name, parameters are
+    __signals__['person-groupname-rebuild'] = (str, str)
+
+    __callback_map = {}
+
     def __init__(self, *args, **kwargs):
         DbReadBase.__init__(self)
         DbWriteBase.__init__(self)
+        Callback.__init__(self)
         self._tables['Person'].update(
             {
                 "handle_func": self.get_person_from_handle, 
@@ -115,6 +210,7 @@ class DictionaryDb(DbWriteBase, DbReadBase):
                 "handles_func": self.get_person_handles,
                 "add_func": self.add_person,
                 "commit_func": self.commit_person,
+                "iter_func": self.iter_people,
             })
         self._tables['Family'].update(
             {
@@ -125,6 +221,7 @@ class DictionaryDb(DbWriteBase, DbReadBase):
                 "handles_func": self.get_family_handles,
                 "add_func": self.add_family,
                 "commit_func": self.commit_family,
+                "iter_func": self.iter_families,
             })
         self._tables['Source'].update(
             {
@@ -135,6 +232,7 @@ class DictionaryDb(DbWriteBase, DbReadBase):
                 "handles_func": self.get_source_handles,
                 "add_func": self.add_source,
                 "commit_func": self.commit_source,
+                "iter_func": self.iter_sources,
                 })
         self._tables['Citation'].update(
             {
@@ -145,6 +243,7 @@ class DictionaryDb(DbWriteBase, DbReadBase):
                 "handles_func": self.get_citation_handles,
                 "add_func": self.add_citation,
                 "commit_func": self.commit_citation,
+                "iter_func": self.iter_citations,
             })
         self._tables['Event'].update(
             {
@@ -155,6 +254,7 @@ class DictionaryDb(DbWriteBase, DbReadBase):
                 "handles_func": self.get_event_handles,
                 "add_func": self.add_event,
                 "commit_func": self.commit_event,
+                "iter_func": self.iter_events,
             })
         self._tables['Media'].update(
             {
@@ -165,6 +265,7 @@ class DictionaryDb(DbWriteBase, DbReadBase):
                 "handles_func": self.get_media_object_handles,
                 "add_func": self.add_object,
                 "commit_func": self.commit_media_object,
+                "iter_func": self.iter_media_objects,
             })
         self._tables['Place'].update(
             {
@@ -175,6 +276,7 @@ class DictionaryDb(DbWriteBase, DbReadBase):
                 "handles_func": self.get_place_handles,
                 "add_func": self.add_place,
                 "commit_func": self.commit_place,
+                "iter_func": self.iter_places,
             })
         self._tables['Repository'].update(
             {
@@ -185,6 +287,7 @@ class DictionaryDb(DbWriteBase, DbReadBase):
                 "handles_func": self.get_repository_handles,
                 "add_func": self.add_repository,
                 "commit_func": self.commit_repository,
+                "iter_func": self.iter_repositories,
             })
         self._tables['Note'].update(
             {
@@ -195,6 +298,7 @@ class DictionaryDb(DbWriteBase, DbReadBase):
                 "handles_func": self.get_note_handles,
                 "add_func": self.add_note,
                 "commit_func": self.commit_note,
+                "iter_func": self.iter_notes,
             })
         self._tables['Tag'].update(
             {
@@ -205,6 +309,7 @@ class DictionaryDb(DbWriteBase, DbReadBase):
                 "handles_func": self.get_tag_handles,
                 "add_func": self.add_tag,
                 "commit_func": self.commit_tag,
+                "iter_func": self.iter_tags,
             })
         # skip GEDCOM cross-ref check for now:
         self.set_feature("skip-check-xref", True)
@@ -218,7 +323,7 @@ class DictionaryDb(DbWriteBase, DbReadBase):
         self.place_bookmarks = Bookmarks()
         self.citation_bookmarks = Bookmarks()
         self.source_bookmarks = Bookmarks()
-        self.repo_bookmarks = Bookmarks()
+        self.repository_bookmarks = Bookmarks()
         self.media_bookmarks = Bookmarks()
         self.note_bookmarks = Bookmarks()
         self.set_person_id_prefix('I%04d')
@@ -249,18 +354,18 @@ class DictionaryDb(DbWriteBase, DbReadBase):
         self.omap_index = 0
         self.rmap_index = 0
         self.nmap_index = 0
-        self.env = None
-        self.person_map      = {}
-        self.family_map      = {}
-        self.place_map       = {}
-        self.citation_map    = {}
-        self.source_map      = {}
-        self.repository_map  = {}
-        self.note_map        = {}
-        self.media_map       = {}
-        self.event_map       = {}
-        self.tag_map         = {}
-        self.metadata   = {}
+        self.env = Environment(self)
+        self.person_map = Map(Table(self._tables["Person"]))
+        self.family_map = Map(Table(self._tables["Family"]))
+        self.place_map  = Map(Table(self._tables["Place"]))
+        self.citation_map = Map(Table(self._tables["Citation"]))
+        self.source_map = Map(Table(self._tables["Source"]))
+        self.repository_map  = Map(Table(self._tables["Repository"]))
+        self.note_map = Map(Table(self._tables["Note"]))
+        self.media_map  = Map(Table(self._tables["Media"]))
+        self.event_map  = Map(Table(self._tables["Event"]))
+        self.tag_map  = Map(Table(self._tables["Tag"]))
+        self.metadata   = Map(Table({"cursor_func": lambda: MetaCursor()}))
         self.name_group = {}
         self.undo_callback = None
         self.redo_callback = None
@@ -286,9 +391,6 @@ class DictionaryDb(DbWriteBase, DbReadBase):
     def transaction_commit(self, txn):
         pass
 
-    def enable_signals(self):
-        pass
-
     def get_undodb(self):
         return None
 
@@ -546,91 +648,107 @@ class DictionaryDb(DbWriteBase, DbReadBase):
         return obj
 
     def get_person_handles(self, sort_handles=False):
-        if sort_handles:
-            raise Exception("Implement!")
-        else:
-            return self.person_map.keys()
+        ## Fixme: implement sort
+        return self.person_map.keys()
 
     def get_family_handles(self):
         return self.family_map.keys()
 
-    def get_event_handles(self):
+    def get_event_handles(self, sort_handles=False):
         return self.event_map.keys()
 
     def get_citation_handles(self, sort_handles=False):
-        if sort_handles:
-            raise Exception("Implement!")
-        else:
-            return self.citation_map.keys()
+        ## Fixme: implement sort
+        return self.citation_map.keys()
 
     def get_source_handles(self, sort_handles=False):
-        if sort_handles:
-            raise Exception("Implement!")
-        else:
-            return self.source_map.keys()
+        ## Fixme: implement sort
+        return self.source_map.keys()
 
     def get_place_handles(self, sort_handles=False):
-        if sort_handles:
-            raise Exception("Implement!")
-        else:
-            return self.place_map.keys()
+        ## Fixme: implement sort
+        return self.place_map.keys()
 
     def get_repository_handles(self):
         return self.repository_map.keys()
 
     def get_media_object_handles(self, sort_handles=False):
-        if sort_handles:
-            raise Exception("Implement!")
-        else:
-            return self.media_map.keys()
+        ## Fixme: implement sort
+        return self.media_map.keys()
 
     def get_note_handles(self):
         return self.note_map.keys()
 
     def get_tag_handles(self, sort_handles=False):
-        if sort_handles:
-            raise Exception("Implement!")
-        else:
-            return self.tag_map.keys()
+        # FIXME: sort
+        return self.tag_map.keys()
 
     def get_event_from_handle(self, handle):
-        return self.event_map[handle]
+        event = None
+        if handle in self.event_map:
+            event = self.event_map[handle]
+        return event
 
     def get_family_from_handle(self, handle): 
-        return self.family_map[handle]
+        family = None
+        if handle in self.family_map:
+            family = self.family_map[handle]
+        return family
 
     def get_repository_from_handle(self, handle):
-        return self.repository_map[handle]
+        repository = None
+        if handle in self.repository_map:
+            repository = self.repository_map[handle]
+        return repository
 
     def get_person_from_handle(self, handle):
-        return self.person_map[handle]
+        person = None
+        if handle in self.person_map:
+            person = self.person_map[handle]
+        return person
 
     def get_place_from_handle(self, handle):
-        place = self.place_map[handle]
+        place = None
+        if handle in self.place_map:
+            place = self.place_map[handle]
         return place
 
     def get_citation_from_handle(self, handle):
-        citation = self.citation_map[handle]
+        citation = None
+        if handle in self.citation_map:
+            citation = self.citation_map[handle]
         return citation
 
     def get_source_from_handle(self, handle):
-        source = self.source_map[handle]
+        source = None
+        if handle in self.source_map:
+            source = self.source_map[handle]
         return source
 
     def get_note_from_handle(self, handle):
-        note = self.note_map[handle]
+        note = None
+        if handle in self.note_map:
+            note = self.note_map[handle]
         return note
 
     def get_object_from_handle(self, handle):
-        media = self.media_map[handle]
+        media = None
+        if handle in self.media_map:
+            media = self.media_map[handle]
         return media
 
     def get_tag_from_handle(self, handle):
-        tag = self.tag_map[handle]
+        tag = None
+        if handle in self.tag_map:
+            tag = self.tag_map[handle]
         return tag
 
     def get_default_person(self):
-        return None
+        handle = self.get_default_handle()
+        if handle:
+            return self.get_person_from_handle(handle)
+        else:
+            return None
 
     def iter_people(self):
         return (person for person in self.person_map.values())
@@ -1203,3 +1321,205 @@ class DictionaryDb(DbWriteBase, DbReadBase):
                 transaction.add(REFERENCE_KEY, TXNDEL, key, old_data, None)
                 #transaction.reference_del.append(str(key))
             self.reference_map.delete(key, txn=txn)
+
+    ## Missing:
+
+    def backup(self):
+        pass
+
+    def close(self):
+        pass
+
+    def find_backlink_handles(self, handle, include_classes=None):
+        return []
+
+    def find_initial_person(self):
+        items = self.person_map.keys()
+        if len(items) > 0:
+            return self.get_person_from_handle(list(items)[0])
+        return None
+
+    def find_place_child_handles(self, handle):
+        return []
+
+    def get_bookmarks(self):
+        return self.bookmarks
+
+    def get_child_reference_types(self):
+        return []
+
+    def get_citation_bookmarks(self):
+        return self.citation_bookmarks
+
+    def get_cursor(self, table, txn=None, update=False, commit=False):
+        pass
+
+    def get_dbname(self):
+        return "DictionaryDb"
+
+    def get_default_handle(self):
+        items = self.person_map.keys()
+        if len(items) > 0:
+            return list(items)[0]
+        return None
+
+    def get_event_attribute_types(self):
+        return []
+
+    def get_event_bookmarks(self):
+        return self.event_bookmarks
+
+    def get_event_roles(self):
+        return []
+
+    def get_event_types(self):
+        return []
+
+    def get_family_attribute_types(self):
+        return []
+
+    def get_family_bookmarks(self):
+        return self.family_bookmarks
+
+    def get_family_event_types(self):
+        return []
+
+    def get_family_relation_types(self):
+        return []
+
+    def get_media_attribute_types(self):
+        return []
+
+    def get_media_bookmarks(self):
+        return self.media_bookmarks
+
+    def get_name_types(self):
+        return []
+
+    def get_note_bookmarks(self):
+        return self.note_bookmarks
+
+    def get_note_types(self):
+        return []
+
+    def get_number_of_records(self, table):
+        return 0
+
+    def get_origin_types(self):
+        return []
+
+    def get_person_attribute_types(self):
+        return []
+
+    def get_person_event_types(self):
+        return []
+
+    def get_place_bookmarks(self):
+        return self.place_bookmarks
+
+    def get_place_tree_cursor(self):
+        return []
+
+    def get_place_types(self):
+        return []
+
+    def get_repo_bookmarks(self):
+        return self.repository_bookmarks
+
+    def get_repository_types(self):
+        return []
+
+    def get_save_path(self):
+        return self._directory
+
+    def get_source_attribute_types(self):
+        return []
+
+    def get_source_bookmarks(self):
+        return self.source_bookmarks
+
+    def get_source_media_types(self):
+        return []
+
+    def get_surname_list(self):
+        return []
+
+    def get_url_types(self):
+        return []
+
+    def has_changed(self):
+        return True
+
+    def is_open(self):
+        return True
+
+    def iter_citation_handles(self):
+        return (key for key in self.citation_map.keys())
+
+    def iter_citations(self):
+        return (key for key in self.citation_map.values())
+
+    def iter_event_handles(self):
+        return (key for key in self.event_map.keys())
+
+    def iter_events(self):
+        return (key for key in self.event_map.values())
+
+    def iter_media_objects(self):
+        return (key for key in self.media_map.values())
+
+    def iter_note_handles(self):
+        return (key for key in self.note_map.keys())
+
+    def iter_notes(self):
+        return (key for key in self.note_map.values())
+
+    def iter_place_handles(self):
+        return (key for key in self.place_map.keys())
+
+    def iter_places(self):
+        return (key for key in self.place_map.values())
+
+    def iter_repositories(self):
+        return (key for key in self.repositories_map.values())
+
+    def iter_repository_handles(self):
+        return (key for key in self.repositories_map.keys())
+
+    def iter_source_handles(self):
+        return (key for key in self.source_map.keys())
+
+    def iter_sources(self):
+        return (key for key in self.source_map.values())
+
+    def iter_tag_handles(self):
+        return (key for key in self.tag_map.keys())
+
+    def iter_tags(self):
+        return (key for key in self.tag_map.values())
+
+    def load(self, directory, pulse_progress, mode, 
+             force_schema_upgrade, 
+             force_bsddb_upgrade, 
+             force_bsddb_downgrade, 
+             force_python_upgrade):
+        self._directory = directory
+
+    def prepare_import(self):
+        pass
+
+    def redo(self, update_history=True):
+        pass
+
+    def restore(self):
+        pass
+
+    def set_prefixes(self, person, media, family, source, citation, 
+                     place, event, repository, note):
+        pass
+
+    def set_save_path(self, directory):
+        self._directory = directory
+
+    def undo(self, update_history=True):
+        pass
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index e0d8c043a..ee6d4162d 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -68,7 +68,7 @@ class Environment(object):
         self.db = db
 
     def txn_begin(self):
-        return DjangoTxn("DbDjango Transaction", self.db)
+        return DjangoTxn("DjangoDb Transaction", self.db)
 
 class Table(object):
     """
@@ -210,6 +210,8 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     # 4. Signal for change in person group name, parameters are
     __signals__['person-groupname-rebuild'] = (str, str)
 
+    __callback_map = {}
+
     def __init__(self, directory=None):
         DbReadBase.__init__(self)
         DbWriteBase.__init__(self)
@@ -1745,8 +1747,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 self.get_number_of_media_objects() == 0 and
                 self.get_number_of_repositories() == 0)
                 
-    __callback_map = {}
-
     def set_prefixes(self, person, media, family, source, citation,
                      place, event, repository, note):
         self.set_person_id_prefix(person)
diff --git a/gramps/webapp/utils.py b/gramps/webapp/utils.py
index 07eddd341..10e0aaf38 100644
--- a/gramps/webapp/utils.py
+++ b/gramps/webapp/utils.py
@@ -143,8 +143,12 @@ def get_person_from_handle(db, handle):
         return None
 
 def probably_alive(handle):
+    ## FIXME: need to call after save?
     person = db.get_person_from_handle(handle)
-    return alive(person, db)
+    if person:
+        return alive(person, db)
+    else:
+        return True
 
 def format_number(number, with_grouping=True):
     if number != "":

From 5115cd13e4518185d604403f4853b4addef9e419 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Wed, 13 May 2015 21:29:07 -0400
Subject: [PATCH 008/105] DictionaryDb: now reads/writes on open/close

---
 gramps/gen/db/dbconst.py                      |  58 +++++--
 .../plugins/database/bsddb_support/write.py   |  33 ----
 gramps/plugins/database/dictionarydb.py       | 142 +++++++++++++++---
 gramps/plugins/database/djangodb.py           |   6 -
 gramps/plugins/importer/importxml.py          |   2 +-
 5 files changed, 170 insertions(+), 71 deletions(-)

diff --git a/gramps/gen/db/dbconst.py b/gramps/gen/db/dbconst.py
index e53535248..21a17db80 100644
--- a/gramps/gen/db/dbconst.py
+++ b/gramps/gen/db/dbconst.py
@@ -28,20 +28,16 @@ Declare constants used by database modules
 # constants
 #
 #-------------------------------------------------------------------------
-__all__ = (
-            ('DBPAGE', 'DBMODE', 'DBCACHE', 'DBLOCKS', 'DBOBJECTS', 'DBUNDO',
-             'DBEXT', 'DBMODE_R', 'DBMODE_W', 'DBUNDOFN', 'DBLOCKFN',
-             'DBRECOVFN','BDBVERSFN', 'DBLOGNAME', 'SCHVERSFN', 'PCKVERSFN',
-             'DBBACKEND'
-            ) +
-            
-            ('PERSON_KEY', 'FAMILY_KEY', 'SOURCE_KEY', 'CITATION_KEY',
-             'EVENT_KEY', 'MEDIA_KEY', 'PLACE_KEY', 'REPOSITORY_KEY',
-             'NOTE_KEY', 'REFERENCE_KEY', 'TAG_KEY'
-            ) +
-
-            ('TXNADD', 'TXNUPD', 'TXNDEL')
-          )
+__all__ = ( 'DBPAGE', 'DBMODE', 'DBCACHE', 'DBLOCKS', 'DBOBJECTS', 'DBUNDO',
+            'DBEXT', 'DBMODE_R', 'DBMODE_W', 'DBUNDOFN', 'DBLOCKFN',
+            'DBRECOVFN','BDBVERSFN', 'DBLOGNAME', 'SCHVERSFN', 'PCKVERSFN',
+            'DBBACKEND',
+            'PERSON_KEY', 'FAMILY_KEY', 'SOURCE_KEY', 'CITATION_KEY',
+            'EVENT_KEY', 'MEDIA_KEY', 'PLACE_KEY', 'REPOSITORY_KEY',
+            'NOTE_KEY', 'REFERENCE_KEY', 'TAG_KEY',
+            'TXNADD', 'TXNUPD', 'TXNDEL',
+            "CLASS_TO_KEY_MAP", "KEY_TO_CLASS_MAP", "KEY_TO_NAME_MAP"
+        )
 
 DBEXT     = ".db"           # File extension to be used for database files
 DBUNDOFN  = "undo.db"       # File name of 'undo' database
@@ -74,3 +70,37 @@ TAG_KEY        = 9
 CITATION_KEY   = 10
 
 TXNADD, TXNUPD, TXNDEL = 0, 1, 2
+
+CLASS_TO_KEY_MAP = {"Person": PERSON_KEY, 
+                    "Family": FAMILY_KEY, 
+                    "Source": SOURCE_KEY, 
+                    "Citation": CITATION_KEY, 
+                    "Event": EVENT_KEY, 
+                    "MediaObject": MEDIA_KEY, 
+                    "Place": PLACE_KEY, 
+                    "Repository": REPOSITORY_KEY,
+                    "Note" : NOTE_KEY,
+                    "Tag": TAG_KEY}
+
+KEY_TO_CLASS_MAP = {PERSON_KEY: "Person", 
+                    FAMILY_KEY: "Family", 
+                    SOURCE_KEY: "Source", 
+                    CITATION_KEY: "Citation", 
+                    EVENT_KEY: "Event", 
+                    MEDIA_KEY: "MediaObject", 
+                    PLACE_KEY: "Place", 
+                    REPOSITORY_KEY: "Repository",
+                    NOTE_KEY: "Note",
+                    TAG_KEY: "Tag"}
+
+KEY_TO_NAME_MAP = {PERSON_KEY: 'person',
+                   FAMILY_KEY: 'family',
+                   EVENT_KEY: 'event',
+                   SOURCE_KEY: 'source',
+                   CITATION_KEY: 'citation',
+                   PLACE_KEY: 'place',
+                   MEDIA_KEY: 'media',
+                   REPOSITORY_KEY: 'repository',
+                   #REFERENCE_KEY: 'reference',
+                   NOTE_KEY: 'note',
+                   TAG_KEY: 'tag'}
diff --git a/gramps/plugins/database/bsddb_support/write.py b/gramps/plugins/database/bsddb_support/write.py
index 920bd2938..836fcc364 100644
--- a/gramps/plugins/database/bsddb_support/write.py
+++ b/gramps/plugins/database/bsddb_support/write.py
@@ -130,39 +130,6 @@ DBERRS      = (db.DBRunRecoveryError, db.DBAccessError,
 # these maps or modifying the values of the keys will break
 # existing databases.
 
-CLASS_TO_KEY_MAP = {Person.__name__: PERSON_KEY, 
-                    Family.__name__: FAMILY_KEY, 
-                    Source.__name__: SOURCE_KEY, 
-                    Citation.__name__: CITATION_KEY, 
-                    Event.__name__: EVENT_KEY, 
-                    MediaObject.__name__: MEDIA_KEY, 
-                    Place.__name__: PLACE_KEY, 
-                    Repository.__name__:REPOSITORY_KEY,
-                    Note.__name__: NOTE_KEY,
-                    Tag.__name__: TAG_KEY}
-
-KEY_TO_CLASS_MAP = {PERSON_KEY: Person.__name__, 
-                    FAMILY_KEY: Family.__name__, 
-                    SOURCE_KEY: Source.__name__, 
-                    CITATION_KEY: Citation.__name__, 
-                    EVENT_KEY: Event.__name__, 
-                    MEDIA_KEY: MediaObject.__name__, 
-                    PLACE_KEY: Place.__name__, 
-                    REPOSITORY_KEY: Repository.__name__,
-                    NOTE_KEY: Note.__name__,
-                    TAG_KEY: Tag.__name__}
-
-KEY_TO_NAME_MAP = {PERSON_KEY: 'person',
-                   FAMILY_KEY: 'family',
-                   EVENT_KEY: 'event',
-                   SOURCE_KEY: 'source',
-                   CITATION_KEY: 'citation',
-                   PLACE_KEY: 'place',
-                   MEDIA_KEY: 'media',
-                   REPOSITORY_KEY: 'repository',
-                   #REFERENCE_KEY: 'reference',
-                   NOTE_KEY: 'note',
-                   TAG_KEY: 'tag'}
 #-------------------------------------------------------------------------
 #
 # Helper functions
diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 621425e2d..09e2451b6 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -28,6 +28,7 @@ import pickle
 import base64
 import time
 import re
+import os
 from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn
 from gramps.gen.utils.callback import Callback
 from gramps.gen.updatecallback import UpdateCallback
@@ -40,7 +41,8 @@ from gramps.gen.db import (PERSON_KEY,
                            PLACE_KEY,
                            REPOSITORY_KEY,
                            NOTE_KEY,
-                           TAG_KEY)
+                           TAG_KEY,
+                           KEY_TO_NAME_MAP)
 
 from gramps.gen.utils.id import create_id
 from gramps.gen.lib.researcher import Researcher
@@ -197,7 +199,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     __callback_map = {}
 
-    def __init__(self, *args, **kwargs):
+    def __init__(self, directory=None):
         DbReadBase.__init__(self)
         DbWriteBase.__init__(self)
         Callback.__init__(self)
@@ -323,7 +325,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.place_bookmarks = Bookmarks()
         self.citation_bookmarks = Bookmarks()
         self.source_bookmarks = Bookmarks()
-        self.repository_bookmarks = Bookmarks()
+        self.repo_bookmarks = Bookmarks()
         self.media_bookmarks = Bookmarks()
         self.note_bookmarks = Bookmarks()
         self.set_person_id_prefix('I%04d')
@@ -373,6 +375,9 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.modified   = 0
         self.txn = DictionaryTxn("DbDictionary Transaction", self)
         self.transaction = None
+        self._directory = directory
+        if directory:
+            self.load(directory)
 
     def version_supported(self):
         """Return True when the file has a supported version."""
@@ -768,24 +773,66 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 return tag
         return None
 
-    def get_family_from_gramps_id(self, gramps_id):
-        for family in self.family_map.values():
-            if family.gramps_id == gramps_id:
-                return family
-        return None
-
     def get_person_from_gramps_id(self, gramps_id):
         for person in self.person_map.values():
             if person.gramps_id == gramps_id:
                 return person
         return None
 
+    def get_family_from_gramps_id(self, gramps_id):
+        for family in self.family_map.values():
+            if family.gramps_id == gramps_id:
+                return family
+        return None
+
+    def get_citation_from_gramps_id(self, gramps_id):
+        for citation in self.citation_map.values():
+            if citation.gramps_id == gramps_id:
+                return citation
+        return None
+
+    def get_source_from_gramps_id(self, gramps_id):
+        for source in self.source_map.values():
+            if source.gramps_id == gramps_id:
+                return source
+        return None
+
+    def get_event_from_gramps_id(self, gramps_id):
+        for event in self.event_map.values():
+            if event.gramps_id == gramps_id:
+                return event
+        return None
+
+    def get_media_from_gramps_id(self, gramps_id):
+        for media in self.media_map.values():
+            if media.gramps_id == gramps_id:
+                return media
+        return None
+
     def get_place_from_gramps_id(self, gramps_id):
         for place in self.place_map.values():
             if place.gramps_id == gramps_id:
                 return place
         return None
 
+    def get_repository_from_gramps_id(self, gramps_id):
+        for repository in self.repository_map.values():
+            if repository.gramps_id == gramps_id:
+                return repository
+        return None
+
+    def get_note_from_gramps_id(self, gramps_id):
+        for note in self.note_map.values():
+            if note.gramps_id == gramps_id:
+                return note
+        return None
+
+    def get_tag_from_gramps_id(self, gramps_id):
+        for tag in self.tag_map.values():
+            if tag.gramps_id == gramps_id:
+                return tag
+        return None
+
     def get_number_of_people(self):
         return len(self.person_map)
 
@@ -1038,33 +1085,73 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return obj.handle
 
     def commit_person(self, person, trans, change_time=None):
+        if person.handle in self.person_map:
+            self.emit("person-update", ([person.handle],))
+        else:
+            self.emit("person-add", ([person.handle],))
         self.person_map[person.handle] = person
 
     def commit_family(self, family, trans, change_time=None):
+        if family.handle in self.family_map:
+            self.emit("family-update", ([family.handle],))
+        else:
+            self.emit("family-add", ([family.handle],))
         self.family_map[family.handle] = family
 
     def commit_citation(self, citation, trans, change_time=None):
+        if citation.handle in self.citation_map:
+            self.emit("citation-update", ([citation.handle],))
+        else:
+            self.emit("citation-add", ([citation.handle],))
         self.citation_map[citation.handle] = citation
 
     def commit_source(self, source, trans, change_time=None):
+        if source.handle in self.source_map:
+            self.emit("source-update", ([source.handle],))
+        else:
+            self.emit("source-add", ([source.handle],))
         self.source_map[source.handle] = source
 
     def commit_repository(self, repository, trans, change_time=None):
+        if repository.handle in self.repository_map:
+            self.emit("repository-update", ([repository.handle],))
+        else:
+            self.emit("repository-add", ([repository.handle],))
         self.repository_map[repository.handle] = repository
 
     def commit_note(self, note, trans, change_time=None):
+        if note.handle in self.note_map:
+            self.emit("note-update", ([note.handle],))
+        else:
+            self.emit("note-add", ([note.handle],))
         self.note_map[note.handle] = note
 
     def commit_place(self, place, trans, change_time=None):
+        if place.handle in self.place_map:
+            self.emit("place-update", ([place.handle],))
+        else:
+            self.emit("place-add", ([place.handle],))
         self.place_map[place.handle] = place
 
     def commit_event(self, event, trans, change_time=None):
+        if event.handle in self.event_map:
+            self.emit("event-update", ([event.handle],))
+        else:
+            self.emit("event-add", ([event.handle],))
         self.event_map[event.handle] = event
 
     def commit_tag(self, tag, trans, change_time=None):
+        if tag.handle in self.tag_map:
+            self.emit("tag-update", ([tag.handle],))
+        else:
+            self.emit("tag-add", ([tag.handle],))
         self.tag_map[tag.handle] = tag
 
     def commit_media_object(self, obj, transaction, change_time=None):
+        if commit.handle in self.commit_map:
+            self.emit("commit-update", ([commit.handle],))
+        else:
+            self.emit("commit-add", ([commit.handle],))
         self.media_map[obj.handle] = obj
 
     def get_gramps_ids(self, obj_key):
@@ -1092,7 +1179,16 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         pass
 
     def request_rebuild(self):
-        pass
+        self.emit('person-rebuild')
+        self.emit('family-rebuild')
+        self.emit('place-rebuild')
+        self.emit('source-rebuild')
+        self.emit('citation-rebuild')
+        self.emit('media-rebuild')
+        self.emit('event-rebuild')
+        self.emit('repository-rebuild')
+        self.emit('note-rebuild')
+        self.emit('tag-rebuild')
 
     def copy_from_db(self, db):
         """
@@ -1163,6 +1259,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.delete_primary_from_reference_map(handle, transaction,
                                                    txn=self.txn)
             self.person_map.delete(handle, txn=self.txn)
+            self.emit("person-delete", ([handle],))
             transaction.add(PERSON_KEY, TXNDEL, handle, person.serialize(), None)
 
     def remove_source(self, handle, transaction):
@@ -1262,6 +1359,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                                    txn=self.txn)
             old_data = data_map.get(handle, txn=self.txn)
             data_map.delete(handle, txn=self.txn)
+            self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],))
             transaction.add(key, TXNDEL, handle, old_data, None)
 
     def delete_primary_from_reference_map(self, handle, transaction, txn=None):
@@ -1328,7 +1426,12 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         pass
 
     def close(self):
-        pass
+        if self._directory:
+            from gramps.plugins.export.exportxml import XmlWriter
+            from gramps.cli.user import User 
+            writer = XmlWriter(self, User(), strip_photos=0, compress=1)
+            filename = os.path.join(self._directory, "data.gramps")
+            writer.write(filename)
 
     def find_backlink_handles(self, handle, include_classes=None):
         return []
@@ -1424,7 +1527,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return []
 
     def get_repo_bookmarks(self):
-        return self.repository_bookmarks
+        return self.repo_bookmarks
 
     def get_repository_types(self):
         return []
@@ -1498,12 +1601,17 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def iter_tags(self):
         return (key for key in self.tag_map.values())
 
-    def load(self, directory, pulse_progress, mode, 
-             force_schema_upgrade, 
-             force_bsddb_upgrade, 
-             force_bsddb_downgrade, 
-             force_python_upgrade):
+    def load(self, directory, pulse_progress=None, mode=None, 
+             force_schema_upgrade=False, 
+             force_bsddb_upgrade=False, 
+             force_bsddb_downgrade=False, 
+             force_python_upgrade=False):
+        from gramps.plugins.importer.importxml import importData
+        from gramps.cli.user import User 
         self._directory = directory
+        filename = os.path.join(directory, "data.gramps")
+        if os.path.isfile(filename):
+            importData(self, filename, User())
 
     def prepare_import(self):
         pass
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index ee6d4162d..56f40f5ac 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -1402,7 +1402,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not person.gramps_id and set_gid:
             person.gramps_id = self.find_next_person_gramps_id()
         self.commit_person(person, trans)
-        self.emit("person-add", ([person.handle],))
         return person.handle
 
     def add_family(self, family, trans, set_gid=True):
@@ -1411,7 +1410,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not family.gramps_id and set_gid:
             family.gramps_id = self.find_next_family_gramps_id()
         self.commit_family(family, trans)
-        self.emit("family-add", ([family.handle],))
         return family.handle
 
     def add_citation(self, citation, trans, set_gid=True):
@@ -1420,7 +1418,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not citation.gramps_id and set_gid:
             citation.gramps_id = self.find_next_citation_gramps_id()
         self.commit_citation(citation, trans)
-        self.emit("citation-add", ([citation.handle],))
         return citation.handle
 
     def add_source(self, source, trans, set_gid=True):
@@ -1429,7 +1426,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not source.gramps_id and set_gid:
             source.gramps_id = self.find_next_source_gramps_id()
         self.commit_source(source, trans)
-        self.emit("source-add", ([source.handle],))
         return source.handle
 
     def add_repository(self, repository, trans, set_gid=True):
@@ -1438,7 +1434,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not repository.gramps_id and set_gid:
             repository.gramps_id = self.find_next_repository_gramps_id()
         self.commit_repository(repository, trans)
-        self.emit("repository-add", ([repository.handle],))
         return repository.handle
 
     def add_note(self, note, trans, set_gid=True):
@@ -1447,7 +1442,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not note.gramps_id and set_gid:
             note.gramps_id = self.find_next_note_gramps_id()
         self.commit_note(note, trans)
-        self.emit("note-add", ([note.handle],))
         return note.handle
 
     def add_place(self, place, trans, set_gid=True):
diff --git a/gramps/plugins/importer/importxml.py b/gramps/plugins/importer/importxml.py
index 4f1a51574..8c978527c 100644
--- a/gramps/plugins/importer/importxml.py
+++ b/gramps/plugins/importer/importxml.py
@@ -57,7 +57,7 @@ from gramps.gen.lib import (Address, Attribute, AttributeType, ChildRef,
                             StyledText, StyledTextTag, StyledTextTagType, 
                             Surname, Tag, Url, PlaceRef)
 from gramps.gen.db import DbTxn
-from gramps.gen.db.write import CLASS_TO_KEY_MAP
+#from gramps.gen.db.write import CLASS_TO_KEY_MAP
 from gramps.gen.errors import GrampsImportError
 from gramps.gen.utils.id import create_id
 from gramps.gen.utils.db import family_name

From e7dc1a7bc44d831a123e3afcdd173a61055e5cf2 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Wed, 13 May 2015 22:01:47 -0400
Subject: [PATCH 009/105] Moved key maps to dbconst

---
 gramps/plugins/database/dictionarydb.py | 15 +++++++--------
 gramps/plugins/importer/importxml.py    |  2 +-
 2 files changed, 8 insertions(+), 9 deletions(-)

diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 09e2451b6..eab579416 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -29,7 +29,7 @@ import base64
 import time
 import re
 import os
-from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn
+from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn, KEY_TO_NAME_MAP
 from gramps.gen.utils.callback import Callback
 from gramps.gen.updatecallback import UpdateCallback
 from gramps.gen.db import (PERSON_KEY,
@@ -41,8 +41,7 @@ from gramps.gen.db import (PERSON_KEY,
                            PLACE_KEY,
                            REPOSITORY_KEY,
                            NOTE_KEY,
-                           TAG_KEY,
-                           KEY_TO_NAME_MAP)
+                           TAG_KEY)
 
 from gramps.gen.utils.id import create_id
 from gramps.gen.lib.researcher import Researcher
@@ -1147,12 +1146,12 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.emit("tag-add", ([tag.handle],))
         self.tag_map[tag.handle] = tag
 
-    def commit_media_object(self, obj, transaction, change_time=None):
-        if commit.handle in self.commit_map:
-            self.emit("commit-update", ([commit.handle],))
+    def commit_media_object(self, media, transaction, change_time=None):
+        if media.handle in self.media_map:
+            self.emit("media-update", ([media.handle],))
         else:
-            self.emit("commit-add", ([commit.handle],))
-        self.media_map[obj.handle] = obj
+            self.emit("media-add", ([media.handle],))
+        self.media_map[media.handle] = media
 
     def get_gramps_ids(self, obj_key):
         key2table = {
diff --git a/gramps/plugins/importer/importxml.py b/gramps/plugins/importer/importxml.py
index 8c978527c..51525dbd6 100644
--- a/gramps/plugins/importer/importxml.py
+++ b/gramps/plugins/importer/importxml.py
@@ -68,7 +68,7 @@ from gramps.gen.display.name import displayer as name_displayer
 from gramps.gen.db.dbconst import (PERSON_KEY, FAMILY_KEY, SOURCE_KEY, 
                                    EVENT_KEY, MEDIA_KEY, PLACE_KEY, 
                                    REPOSITORY_KEY, NOTE_KEY, TAG_KEY, 
-                                   CITATION_KEY)
+                                   CITATION_KEY, CLASS_TO_KEY_MAP)
 from gramps.gen.updatecallback import UpdateCallback
 from gramps.version import VERSION
 from gramps.gen.config import config

From 73886e9232129340b6c7625db4e0201c0b3b194a Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Wed, 13 May 2015 22:55:23 -0400
Subject: [PATCH 010/105] DictionaryDb: implement delete

---
 gramps/plugins/database/dictionarydb.py | 36 +++++++------------------
 1 file changed, 9 insertions(+), 27 deletions(-)

diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index eab579416..647f6b0ee 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -999,6 +999,11 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             return self.tag_map[handle].serialize()
         return None
 
+    def get_raw_event_data(self, handle):
+        if handle in self.event_map:
+            return self.event_map[handle].serialize()
+        return None
+
     def add_person(self, person, trans, set_gid=True):
         if not person.handle:
             person.handle = create_id()
@@ -1244,22 +1249,9 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
         if self.readonly or not handle:
             return
-        person = self.get_person_from_handle(handle)
-        #self.genderStats.uncount_person (person)
-        #self.remove_from_surname_list(person)
-        if isinstance(handle, str):
-            handle = handle.encode('utf-8')
-        if transaction.batch:
-            with BSDDBTxn(self.env, self.person_map) as txn:            
-                self.delete_primary_from_reference_map(handle, transaction,
-                                                       txn=txn.txn)
-                txn.delete(handle)
-        else:
-            self.delete_primary_from_reference_map(handle, transaction,
-                                                   txn=self.txn)
-            self.person_map.delete(handle, txn=self.txn)
+        if handle in self.person_map:
+            del self.person_map[handle]
             self.emit("person-delete", ([handle],))
-            transaction.add(PERSON_KEY, TXNDEL, handle, person.serialize(), None)
 
     def remove_source(self, handle, transaction):
         """
@@ -1345,21 +1337,11 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def __do_remove(self, handle, transaction, data_map, key):
         if self.readonly or not handle:
             return
-
         if isinstance(handle, str):
             handle = handle.encode('utf-8')
-        if transaction.batch:
-            with BSDDBTxn(self.env, data_map) as txn:
-                self.delete_primary_from_reference_map(handle, transaction,
-                                                        txn=txn.txn)
-                txn.delete(handle)
-        else:
-            self.delete_primary_from_reference_map(handle, transaction,
-                                                   txn=self.txn)
-            old_data = data_map.get(handle, txn=self.txn)
-            data_map.delete(handle, txn=self.txn)
+        if handle in data_map:
+            del data_map[handle]
             self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],))
-            transaction.add(key, TXNDEL, handle, old_data, None)
 
     def delete_primary_from_reference_map(self, handle, transaction, txn=None):
         """

From ca88f37bb6200bb340a3481d5fd83423e4d71fa7 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Thu, 14 May 2015 06:31:59 -0400
Subject: [PATCH 011/105] Database backends: bsddb, django, and dictionary

---
 gramps/cli/clidbman.py                        |  11 +-
 gramps/gui/dbman.py                           |  44 +++--
 gramps/plugins/database/dictionarydb.py       |  20 ++-
 .../defaults/default_settings.py              | 150 ++++++++++++++++++
 .../django_support/defaults/sqlite.db         | Bin 0 -> 293888 bytes
 gramps/plugins/database/djangodb.py           |  23 ++-
 6 files changed, 230 insertions(+), 18 deletions(-)
 create mode 100644 gramps/plugins/database/django_support/defaults/default_settings.py
 create mode 100644 gramps/plugins/database/django_support/defaults/sqlite.db

diff --git a/gramps/cli/clidbman.py b/gramps/cli/clidbman.py
index 7c77876f1..eb7a59ffa 100644
--- a/gramps/cli/clidbman.py
+++ b/gramps/cli/clidbman.py
@@ -225,7 +225,7 @@ class CLIDbManager(object):
         """
         print(_('Import finished...'))
 
-    def create_new_db_cli(self, title=None, create_db=True):
+    def create_new_db_cli(self, title=None, create_db=True, dbid=None):
         """
         Create a new database.
         """
@@ -244,8 +244,9 @@ class CLIDbManager(object):
 
         if create_db:
             # write the version number into metadata
-            
-            newdb = self.dbstate.make_database("bsddb")
+            if dbid is None:
+                dbid = "bsddb"
+            newdb = self.dbstate.make_database(dbid)
             newdb.write_version(new_path)
 
         (tval, last) = time_val(new_path)
@@ -254,11 +255,11 @@ class CLIDbManager(object):
                                    last, tval, False, ""))
         return new_path, title
 
-    def _create_new_db(self, title=None):
+    def _create_new_db(self, title=None, dbid=None):
         """
         Create a new database, do extra stuff needed
         """
-        return self.create_new_db_cli(title)
+        return self.create_new_db_cli(title, dbid=dbid)
 
     def import_new_db(self, filename, user):
         """
diff --git a/gramps/gui/dbman.py b/gramps/gui/dbman.py
index 60f28bb6f..d32e86ebb 100644
--- a/gramps/gui/dbman.py
+++ b/gramps/gui/dbman.py
@@ -72,7 +72,7 @@ from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
 from gramps.gen.const import URL_WIKISTRING
 from .user import User
-from .dialog import ErrorDialog, QuestionDialog, QuestionDialog2
+from .dialog import ErrorDialog, QuestionDialog, QuestionDialog2, ICON
 from .pluginmanager import GuiPluginManager
 from gramps.cli.clidbman import CLIDbManager, NAME_FILE, time_val
 from .ddtargets import DdTargets
@@ -80,7 +80,6 @@ from gramps.gen.recentfiles import rename_filename, remove_filename
 from .glade import Glade
 from gramps.gen.db.exceptions import DbException
 
-
 _RETURN = Gdk.keyval_from_name("Return")
 _KP_ENTER = Gdk.keyval_from_name("KP_Enter")
 
@@ -104,6 +103,25 @@ STOCK_COL = 6
 
 RCS_BUTTON = { True : _('_Extract'), False : _('_Archive') }
 
+class DatabaseDialog(Gtk.MessageDialog):
+    def __init__(self, parent=None):
+        Gtk.MessageDialog.__init__(self,
+                                parent,
+                                flags=Gtk.DialogFlags.MODAL,
+                                type=Gtk.MessageType.QUESTION,
+                                   )
+        self.set_icon(ICON)
+        self.set_title('')
+
+        self.set_markup('<span size="larger" weight="bold">%s</span>' %
+                        _('Database Backend'))
+        self.format_secondary_text(
+            _("Please select a database backend type"))
+
+        self.add_button("BSDDB Database (standard)", 1)
+        self.add_button("Dictionary (in-memory)", 2)
+        self.add_button("Django Database", 3)
+
 class DbManager(CLIDbManager):
     """
     Database Manager. Opens a database manager window that allows users to
@@ -765,19 +783,27 @@ class DbManager(CLIDbManager):
         message.
         """
         self.new.set_sensitive(False)
-        try:
-            self._create_new_db()
-        except (OSError, IOError) as msg:
-            DbManager.ERROR(_("Could not create Family Tree"),
-                                       str(msg))
+        # popup window and ask for dbid types, if more than one
+        ## FIXME: autoload from plugins
+        dbid = "bsddb"
+        d = DatabaseDialog(self.top)
+        database = d.run()
+        d.destroy()
+        if database >= 0:
+            dbid = {1:"bsddb",2:"dictionarydb",3:"djangodb"}[database]
+            try:
+                self._create_new_db(dbid=dbid)
+            except (OSError, IOError) as msg:
+                DbManager.ERROR(_("Could not create Family Tree"),
+                                str(msg))
         self.new.set_sensitive(True)
 
-    def _create_new_db(self, title=None, create_db=True):
+    def _create_new_db(self, title=None, create_db=True, dbid=None):
         """
         Create a new database, append to model
         """
         new_path, title = self.create_new_db_cli(conv_to_unicode(title, 'utf8'),
-                                                 create_db)
+                                                 create_db, dbid)
         path_name = os.path.join(new_path, NAME_FILE)
         (tval, last) = time_val(new_path)
         node = self.model.append(None, [title, new_path, path_name, 
diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 647f6b0ee..936d8f534 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -21,7 +21,7 @@
 
 #------------------------------------------------------------------------
 #
-# Gramps Modules
+# Python Modules
 #
 #------------------------------------------------------------------------
 import pickle
@@ -29,7 +29,15 @@ import base64
 import time
 import re
 import os
+import logging
+
+#------------------------------------------------------------------------
+#
+# Gramps Modules
+#
+#------------------------------------------------------------------------
 from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn, KEY_TO_NAME_MAP
+from gramps.gen.db.dbconst import *
 from gramps.gen.utils.callback import Callback
 from gramps.gen.updatecallback import UpdateCallback
 from gramps.gen.db import (PERSON_KEY,
@@ -56,6 +64,8 @@ from gramps.gen.lib.repo import Repository
 from gramps.gen.lib.note import Note
 from gramps.gen.lib.tag import Tag
 
+_LOG = logging.getLogger(DBLOGNAME)
+
 class Environment(object):
     """
     Implements the Environment API.
@@ -1612,3 +1622,11 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def undo(self, update_history=True):
         pass
+
+    def write_version(self, directory):
+        """Write files for a newly created DB."""
+        versionpath = os.path.join(directory, str(DBBACKEND))
+        _LOG.debug("Write database backend file to 'dictionarydb'")
+        with open(versionpath, "w") as version_file:
+            version_file.write("dictionarydb")
+
diff --git a/gramps/plugins/database/django_support/defaults/default_settings.py b/gramps/plugins/database/django_support/defaults/default_settings.py
new file mode 100644
index 000000000..fae3ffd06
--- /dev/null
+++ b/gramps/plugins/database/django_support/defaults/default_settings.py
@@ -0,0 +1,150 @@
+import os
+from gramps.gen.const import DATA_DIR
+
+WEB_DIR = os.path.dirname(os.path.realpath(__file__))
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+INTERNAL_IPS = ('127.0.0.1',)
+
+ADMINS = (
+    ('admin', 'your_email@domain.com'),
+)
+
+MANAGERS = ADMINS
+DATABASE_ROUTERS = []
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3',
+        'NAME': os.path.join(WEB_DIR, 'sqlite.db'),
+    }
+}
+DATABASE_ENGINE = 'sqlite3'           
+DATABASE_NAME = os.path.join(WEB_DIR, 'sqlite.db')
+DATABASE_USER = ''             
+DATABASE_PASSWORD = ''         
+DATABASE_HOST = ''             
+DATABASE_PORT = ''             
+TIME_ZONE = 'America/New_York'
+LANGUAGE_CODE = 'en-us'
+SITE_ID = 1
+USE_I18N = True
+MEDIA_ROOT = ''
+MEDIA_URL = ''
+ADMIN_MEDIA_PREFIX = '/gramps-media/'
+SECRET_KEY = 'zd@%vslj5sqhx94_8)0hsx*rk9tj3^ly$x+^*tq4bggr&uh$ac'
+
+TEMPLATE_LOADERS = (
+    'django.template.loaders.filesystem.Loader', # 1.4
+    'django.template.loaders.app_directories.Loader', # 1.4
+   #'django.template.loaders.filesystem.load_template_source',
+   #'django.template.loaders.app_directories.load_template_source',
+)
+
+MIDDLEWARE_CLASSES = (
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.middleware.common.CommonMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+#   'debug_toolbar.middleware.DebugToolbarMiddleware',
+)
+
+ROOT_URLCONF = 'gramps.webapp.urls'
+STATIC_URL = '/static/' # 1.4
+
+TEMPLATE_DIRS = (
+    # Use absolute paths, not relative paths.
+    os.path.join(DATA_DIR, "templates"),
+)
+
+TEMPLATE_CONTEXT_PROCESSORS = (
+    "django.contrib.auth.context_processors.auth", # 1.4
+    "django.contrib.messages.context_processors.messages", # 1.4
+#   "django.core.context_processors.auth",
+#   "django.core.context_processors.debug",
+    "django.core.context_processors.i18n",
+    "django.core.context_processors.media",
+    "gramps.webapp.grampsdb.views.context_processor",
+    "gramps.webapp.context.messages",
+)
+
+INSTALLED_APPS = (
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.staticfiles',
+    'django.contrib.messages', # 1.4
+    'django.contrib.sites',
+    'django.contrib.admin',
+    'gramps.webapp.grampsdb',
+#    'django_extensions',
+#    'debug_toolbar',
+)
+
+DEBUG_TOOLBAR_PANELS = (
+    'debug_toolbar.panels.version.VersionDebugPanel',
+    'debug_toolbar.panels.timer.TimerDebugPanel',
+    'debug_toolbar.panels.settings_vars.SettingsVarsDebugPanel',
+    'debug_toolbar.panels.headers.HeaderDebugPanel',
+    'debug_toolbar.panels.request_vars.RequestVarsDebugPanel',
+    'debug_toolbar.panels.template.TemplateDebugPanel',
+    'debug_toolbar.panels.sql.SQLDebugPanel',
+    'debug_toolbar.panels.signals.SignalDebugPanel',
+    'debug_toolbar.panels.logger.LoggingPanel',
+    )
+
+def custom_show_toolbar(request):
+    return True # Always show toolbar, for example purposes only.
+
+DEBUG_TOOLBAR_CONFIG = {
+    'INTERCEPT_REDIRECTS': False,
+#    'SHOW_TOOLBAR_CALLBACK': custom_show_toolbar,
+#    'EXTRA_SIGNALS': ['myproject.signals.MySignal'],
+    'HIDE_DJANGO_SQL': False,
+    }
+
+AUTH_PROFILE_MODULE = "grampsdb.Profile"
+
+# Had to add these to use settings.configure():
+DATABASE_OPTIONS = ''
+URL_VALIDATOR_USER_AGENT = ''
+DEFAULT_INDEX_TABLESPACE = ''
+DEFAULT_TABLESPACE = ''
+CACHE_BACKEND = 'locmem://'
+TRANSACTIONS_MANAGED = False
+LOCALE_PATHS = tuple()
+
+# Changes for Django 1.3:
+USE_L10N = True
+FORMAT_MODULE_PATH = ""
+## End Changes for Django 1.3
+
+# Changes for Django 1.4:
+USE_TZ = False
+## End Changes for Django 1.4
+
+# Changes for Django 1.5:
+CACHES = {
+    'default': {
+        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
+        }
+    }
+DEFAULT_CHARSET = "utf-8"
+## End Changes for Django 1.5
+
+## Changes for Django 1.5.4:
+LOGGING_CONFIG = None
+AUTH_USER_MODEL = 'auth.User'
+## End Changes for Django 1.5.4
+
+LOGIN_URL = "/login/"
+LOGOUT_URL = "/logout"
+LOGIN_REDIRECT_URL = "/"
+
+## Changes for Django 1.6:
+LOGGING = None
+
+## Changes for Django 1.7.1:
+ABSOLUTE_URL_OVERRIDES = {}
diff --git a/gramps/plugins/database/django_support/defaults/sqlite.db b/gramps/plugins/database/django_support/defaults/sqlite.db
new file mode 100644
index 0000000000000000000000000000000000000000..a9ac911f065f752dfb25be75e2916ff5df508451
GIT binary patch
literal 293888
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
N%rWbcAX@iQ{{@Gr__6>1

literal 0
HcmV?d00001

diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index 56f40f5ac..e380c9601 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -30,6 +30,9 @@ import re
 import base64
 import pickle
 import os
+import logging
+import shutil
+from django.db import transaction
 
 #------------------------------------------------------------------------
 #
@@ -53,13 +56,13 @@ from gramps.gen.db import (PERSON_KEY,
                     REPOSITORY_KEY,
                     NOTE_KEY)
 from gramps.gen.utils.id import create_id
-from django.db import transaction
+from gramps.gen.db.dbconst import *
 
 ## add this directory to sys path, so we can find django_support later:
-import sys
-import os
 sys.path.append(os.path.dirname(os.path.abspath(__file__)))
 
+_LOG = logging.getLogger(DBLOGNAME)
+
 class Environment(object):
     """
     Implements the Environment API.
@@ -2041,3 +2044,17 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def restore(self):
         pass
+
+    def write_version(self, directory):
+        """Write files for a newly created DB."""
+        versionpath = os.path.join(directory, str(DBBACKEND))
+        _LOG.debug("Write database backend file to 'djangodb'")
+        with open(versionpath, "w") as version_file:
+            version_file.write("djangodb")
+        # Write default_settings, sqlite.db
+        defaults = os.path.join(os.path.dirname(os.path.abspath(__file__)),
+                                "django_support", "defaults")
+        _LOG.debug("Copy defaults from: " + defaults)
+        for filename in os.listdir(defaults):
+            fullpath = os.path.abspath(os.path.join(defaults, filename))
+            shutil.copy2(fullpath, directory)

From 28c609d4c7a19273fb86019c10c09428af0ea800 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Thu, 14 May 2015 07:01:47 -0400
Subject: [PATCH 012/105] Hack to reset modules on subsequent uses of Django
 databases

---
 gramps/gen/dbstate.py | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py
index 04a8e683d..069934a13 100644
--- a/gramps/gen/dbstate.py
+++ b/gramps/gen/dbstate.py
@@ -22,6 +22,7 @@
 """
 Provide the database state class
 """
+import sys
 
 from .db import DbReadBase
 from .proxy.proxybase import ProxyDbBase
@@ -139,6 +140,34 @@ class DbState(Callback):
             pmgr.reg_plugins(USER_PLUGINS, self, None, load_on_reg=True)
             pdata = pmgr.get_plugin(id)
 
+        ### FIXME: Currently Django needs to reset modules
+        if pdata.id == "djangodb":
+            if self.modules_is_set():
+                self.reset_modules()
+            else:
+                self.save_modules()
+
         mod = pmgr.load_plugin(pdata)
         database = getattr(mod, pdata.databaseclass)
         return database()
+
+    ## FIXME:
+    ## Work-around for databases that need sys refresh (django):
+    def modules_is_set(self):
+        if hasattr(self, "_modules"):
+            return self._modules != None
+        else:
+            self._modules = None
+            return False
+
+    def reset_modules(self):
+        # First, clear out old modules:
+        for key in list(sys.modules.keys()):
+            del(sys.modules[key])
+        # Next, restore previous:
+        for key in self._modules:
+            sys.modules[key] = self._modules[key]
+
+    def save_modules(self):
+        self._modules = sys.modules.copy()
+

From 47a3a7ad0f1325e0ac6104e9c3f0dd65a0178ef9 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Thu, 14 May 2015 09:07:10 -0400
Subject: [PATCH 013/105] Reworked backend Cursors; don't emit changes when
 changing in batch mode

---
 gramps/plugins/database/__init__.py     |    0
 gramps/plugins/database/dbdjango.py     | 2036 -----------------------
 gramps/plugins/database/dictionarydb.py |  106 +-
 gramps/plugins/database/djangodb.py     |  103 +-
 4 files changed, 121 insertions(+), 2124 deletions(-)
 create mode 100644 gramps/plugins/database/__init__.py
 delete mode 100644 gramps/plugins/database/dbdjango.py

diff --git a/gramps/plugins/database/__init__.py b/gramps/plugins/database/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/gramps/plugins/database/dbdjango.py b/gramps/plugins/database/dbdjango.py
deleted file mode 100644
index adb28dc54..000000000
--- a/gramps/plugins/database/dbdjango.py
+++ /dev/null
@@ -1,2036 +0,0 @@
-# Gramps - a GTK+/GNOME based genealogy program
-#
-# Copyright (C) 2009         Douglas S. Blank <doug.blank@gmail.com>
-#
-# 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; either version 2 of the License, or
-# (at your option) any later version.
-#
-# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-
-""" Implements a Db interface """
-
-#------------------------------------------------------------------------
-#
-# Python Modules
-#
-#------------------------------------------------------------------------
-import sys
-import time
-import re
-import base64
-import pickle
-import os
-
-#------------------------------------------------------------------------
-#
-# Gramps Modules
-#
-#------------------------------------------------------------------------
-import gramps
-from gramps.gen.lib import (Person, Family, Event, Place, Repository, 
-                            Citation, Source, Note, MediaObject, Tag, 
-                            Researcher)
-from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn
-from gramps.gen.utils.callback import Callback
-from gramps.gen.updatecallback import UpdateCallback
-from gramps.gen.db import (PERSON_KEY,
-                    FAMILY_KEY,
-                    CITATION_KEY,
-                    SOURCE_KEY,
-                    EVENT_KEY,
-                    MEDIA_KEY,
-                    PLACE_KEY,
-                    REPOSITORY_KEY,
-                    NOTE_KEY)
-from gramps.gen.utils.id import create_id
-from django.db import transaction
-
-class Environment(object):
-    """
-    Implements the Environment API.
-    """
-    def __init__(self, db):
-        self.db = db
-
-    def txn_begin(self):
-        return DjangoTxn("DbDjango Transaction", self.db)
-
-class Table(object):
-    """
-    Implements Table interface.
-    """
-    def __init__(self, funcs):
-        self.funcs = funcs
-
-    def cursor(self):
-        """
-        Returns a Cursor for this Table.
-        """
-        return self.funcs["cursor_func"]()
-
-    def put(key, data, txn=None):
-        self[key] = data
-
-class Map(dict):
-    """
-    Implements the map API for person_map, etc.
-    
-    Takes a Table() as argument.
-    """
-    def __init__(self, tbl, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self.db = tbl
-
-class MetaCursor(object):
-    def __init__(self):
-        pass
-    def __enter__(self):
-        return self
-    def __iter__(self):
-        return self.__next__()
-    def __next__(self):
-        yield None
-    def __exit__(self, *args, **kwargs):
-        pass
-    def iter(self):
-        yield None
-    def first(self):
-        self._iter = self.__iter__()
-        return self.next()
-    def next(self):
-        try:
-            return next(self._iter)
-        except:
-            return None
-    def close(self):
-        pass
-
-class Cursor(object):
-    def __init__(self, model, func):
-        self.model = model
-        self.func = func
-    def __enter__(self):
-        return self
-    def __iter__(self):
-        return self.__next__()
-    def __next__(self):
-        for item in self.model.all():
-            yield (bytes(item.handle, "utf-8"), self.func(item.handle))
-    def __exit__(self, *args, **kwargs):
-        pass
-    def iter(self):
-        for item in self.model.all():
-            yield (bytes(item.handle, "utf-8"), self.func(item.handle))
-        yield None
-    def first(self):
-        self._iter = self.__iter__()
-        return self.next()
-    def next(self):
-        try:
-            return next(self._iter)
-        except:
-            return None
-    def close(self):
-        pass
-
-class Bookmarks(object):
-    def __init__(self):
-        self.handles = []
-    def get(self):
-        return self.handles
-    def append(self, handle):
-        self.handles.append(handle)
-
-class DjangoTxn(DbTxn):
-    def __init__(self, message, db, table=None):
-        DbTxn.__init__(self, message, db)
-        self.table = table
-
-    def get(self, key, default=None, txn=None, **kwargs):
-        """
-        Returns the data object associated with key
-        """
-        try:
-            return self.table.objects(handle=key)
-        except:
-            if txn and key in txn:
-                return txn[key]
-            else:
-                return None
-
-    def put(self, handle, new_data, txn):
-        """
-        """
-        txn[handle] = new_data
-
-    def commit(self):
-        pass
-
-class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
-    """
-    A Gramps Database Backend. This replicates the grampsdb functions.
-    """
-    # Set up dictionary for callback signal handler
-    # ---------------------------------------------
-    # 1. Signals for primary objects
-    __signals__ = dict((obj+'-'+op, signal)
-                       for obj in
-                       ['person', 'family', 'event', 'place',
-                        'source', 'citation', 'media', 'note', 'repository', 'tag']
-                       for op, signal in zip(
-                               ['add',   'update', 'delete', 'rebuild'],
-                               [(list,), (list,),  (list,),   None]
-                       )
-                   )
-    
-    # 2. Signals for long operations
-    __signals__.update(('long-op-'+op, signal) for op, signal in zip(
-        ['start',  'heartbeat', 'end'],
-        [(object,), None,       None]
-        ))
-
-    # 3. Special signal for change in home person
-    __signals__['home-person-changed'] = None
-
-    # 4. Signal for change in person group name, parameters are
-    __signals__['person-groupname-rebuild'] = (str, str)
-
-    def __init__(self, directory=None):
-        DbReadBase.__init__(self)
-        DbWriteBase.__init__(self)
-        Callback.__init__(self)
-        self._tables = {
-            'Person':
-                {
-                "handle_func": self.get_person_from_handle, 
-                "gramps_id_func": self.get_person_from_gramps_id,
-                "class_func": gramps.gen.lib.Person,
-                "cursor_func": self.get_person_cursor,
-                "handles_func": self.get_person_handles,
-                "iter_func": self.iter_people,
-                },
-            'Family':
-                {
-                "handle_func": self.get_family_from_handle, 
-                "gramps_id_func": self.get_family_from_gramps_id,
-                "class_func": gramps.gen.lib.Family,
-                "cursor_func": self.get_family_cursor,
-                "handles_func": self.get_family_handles,
-                "iter_func": self.iter_families,
-                },
-            'Source':
-                {
-                "handle_func": self.get_source_from_handle, 
-                "gramps_id_func": self.get_source_from_gramps_id,
-                "class_func": gramps.gen.lib.Source,
-                "cursor_func": self.get_source_cursor,
-                "handles_func": self.get_source_handles,
-                "iter_func": self.iter_sources,
-                },
-            'Citation':
-                {
-                "handle_func": self.get_citation_from_handle, 
-                "gramps_id_func": self.get_citation_from_gramps_id,
-                "class_func": gramps.gen.lib.Citation,
-                "cursor_func": self.get_citation_cursor,
-                "handles_func": self.get_citation_handles,
-                "iter_func": self.iter_citations,
-                },
-            'Event':
-                {
-                "handle_func": self.get_event_from_handle, 
-                "gramps_id_func": self.get_event_from_gramps_id,
-                "class_func": gramps.gen.lib.Event,
-                "cursor_func": self.get_event_cursor,
-                "handles_func": self.get_event_handles,
-                "iter_func": self.iter_events,
-                },
-            'Media':
-                {
-                "handle_func": self.get_object_from_handle, 
-                "gramps_id_func": self.get_object_from_gramps_id,
-                "class_func": gramps.gen.lib.MediaObject,
-                "cursor_func": self.get_media_cursor,
-                "handles_func": self.get_media_object_handles,
-                "iter_func": self.iter_media_objects,
-                },
-            'Place':
-                {
-                "handle_func": self.get_place_from_handle, 
-                "gramps_id_func": self.get_place_from_gramps_id,
-                "class_func": gramps.gen.lib.Place,
-                "cursor_func": self.get_place_cursor,
-                "handles_func": self.get_place_handles,
-                "iter_func": self.iter_places,
-                },
-            'Repository':
-                {
-                "handle_func": self.get_repository_from_handle, 
-                "gramps_id_func": self.get_repository_from_gramps_id,
-                "class_func": gramps.gen.lib.Repository,
-                "cursor_func": self.get_repository_cursor,
-                "handles_func": self.get_repository_handles,
-                "iter_func": self.iter_repositories,
-                },
-            'Note':
-                {
-                "handle_func": self.get_note_from_handle, 
-                "gramps_id_func": self.get_note_from_gramps_id,
-                "class_func": gramps.gen.lib.Note,
-                "cursor_func": self.get_note_cursor,
-                "handles_func": self.get_note_handles,
-                "iter_func": self.iter_notes,
-                },
-            'Tag':
-                {
-                "handle_func": self.get_tag_from_handle, 
-                "gramps_id_func": None,
-                "class_func": gramps.gen.lib.Tag,
-                "cursor_func": self.get_tag_cursor,
-                "handles_func": self.get_tag_handles,
-                "iter_func": self.iter_tags,
-                },
-            }
-        # skip GEDCOM cross-ref check for now:
-        self.set_feature("skip-check-xref", True)
-        self.readonly = False
-        self.db_is_open = True
-        self.name_formats = []
-        self.bookmarks = Bookmarks()
-        self.undo_callback = None
-        self.redo_callback = None
-        self.undo_history_callback = None
-        self.modified   = 0
-        self.txn = DjangoTxn("DbDjango Transaction", self)
-        self.transaction = None
-        # Import cache for gedcom import, uses transactions, and
-        # two step adding of objects.
-        self.import_cache = {}
-        self.use_import_cache = False
-        self.use_db_cache = True
-        self._directory = directory
-        if directory:
-            self.load(directory)
-
-    def load(self, directory, pulse_progress=None, mode=None):
-        self._directory = directory
-        from django.conf import settings
-        default_settings = {}
-        settings_file = os.path.join(directory, "default_settings.py")
-        with open(settings_file) as f:
-            code = compile(f.read(), settings_file, 'exec')
-            exec(code, globals(), default_settings)
-
-        class Module(object):
-            def __init__(self, dictionary):
-                self.dictionary = dictionary
-            def __getattr__(self, item):
-                return self.dictionary[item]
-
-        try:
-            settings.configure(Module(default_settings))
-        except RuntimeError:
-            # already configured; ignore
-            pass
-
-        import django
-        django.setup()
-
-        from django_support.libdjango import DjangoInterface
-        self.dji = DjangoInterface()
-        self.family_bookmarks = Bookmarks()
-        self.event_bookmarks = Bookmarks()
-        self.place_bookmarks = Bookmarks()
-        self.citation_bookmarks = Bookmarks()
-        self.source_bookmarks = Bookmarks()
-        self.repo_bookmarks = Bookmarks()
-        self.media_bookmarks = Bookmarks()
-        self.note_bookmarks = Bookmarks()
-        self.set_person_id_prefix('I%04d')
-        self.set_object_id_prefix('O%04d')
-        self.set_family_id_prefix('F%04d')
-        self.set_citation_id_prefix('C%04d')
-        self.set_source_id_prefix('S%04d')
-        self.set_place_id_prefix('P%04d')
-        self.set_event_id_prefix('E%04d')
-        self.set_repository_id_prefix('R%04d')
-        self.set_note_id_prefix('N%04d')
-        # ----------------------------------
-        self.id_trans  = DjangoTxn("ID Transaction", self, self.dji.Person)
-        self.fid_trans = DjangoTxn("FID Transaction", self, self.dji.Family)
-        self.pid_trans = DjangoTxn("PID Transaction", self, self.dji.Place)
-        self.cid_trans = DjangoTxn("CID Transaction", self, self.dji.Citation)
-        self.sid_trans = DjangoTxn("SID Transaction", self, self.dji.Source)
-        self.oid_trans = DjangoTxn("OID Transaction", self, self.dji.Media)
-        self.rid_trans = DjangoTxn("RID Transaction", self, self.dji.Repository)
-        self.nid_trans = DjangoTxn("NID Transaction", self, self.dji.Note)
-        self.eid_trans = DjangoTxn("EID Transaction", self, self.dji.Event)
-        self.cmap_index = 0
-        self.smap_index = 0
-        self.emap_index = 0
-        self.pmap_index = 0
-        self.fmap_index = 0
-        self.lmap_index = 0
-        self.omap_index = 0
-        self.rmap_index = 0
-        self.nmap_index = 0
-        self.env = Environment(self)
-        self.person_map = Map(Table(self._tables["Person"]))
-        self.family_map = Map(Table(self._tables["Family"]))
-        self.place_map  = Map(Table(self._tables["Place"]))
-        self.citation_map = Map(Table(self._tables["Citation"]))
-        self.source_map = Map(Table(self._tables["Source"]))
-        self.repository_map  = Map(Table(self._tables["Repository"]))
-        self.note_map = Map(Table(self._tables["Note"]))
-        self.media_map  = Map(Table(self._tables["Media"]))
-        self.event_map  = Map(Table(self._tables["Event"]))
-        self.tag_map  = Map(Table(self._tables["Tag"]))
-        self.metadata   = Map(Table({"cursor_func": lambda: MetaCursor()}))
-        self.name_group = {}
-        self.event_names = set()
-        self.individual_attributes = set()
-        self.family_attributes = set()
-        self.source_attributes = set()
-        self.child_ref_types = set()
-        self.family_rel_types = set()
-        self.event_role_names = set()
-        self.name_types = set()
-        self.origin_types = set()
-        self.repository_types = set()
-        self.note_types = set()
-        self.source_media_types = set()
-        self.url_types = set()
-        self.media_attributes = set()
-        self.place_types = set()
-
-    def prepare_import(self):
-        """
-        DbDjango does not commit data on gedcom import, but saves them
-        for later commit.
-        """
-        self.use_import_cache = True
-        self.import_cache = {}
-
-    @transaction.atomic
-    def commit_import(self):
-        """
-        Commits the items that were queued up during the last gedcom
-        import for two step adding.
-        """
-        # First we add the primary objects:
-        for key in list(self.import_cache.keys()):
-            obj = self.import_cache[key]
-            if isinstance(obj, Person):
-                self.dji.add_person(obj.serialize())
-            elif isinstance(obj, Family):
-                self.dji.add_family(obj.serialize())
-            elif isinstance(obj, Event):
-                self.dji.add_event(obj.serialize())
-            elif isinstance(obj, Place):
-                self.dji.add_place(obj.serialize())
-            elif isinstance(obj, Repository):
-                self.dji.add_repository(obj.serialize())
-            elif isinstance(obj, Citation):
-                self.dji.add_citation(obj.serialize())
-            elif isinstance(obj, Source):
-                self.dji.add_source(obj.serialize())
-            elif isinstance(obj, Note):
-                self.dji.add_note(obj.serialize())
-            elif isinstance(obj, MediaObject):
-                self.dji.add_media(obj.serialize())
-            elif isinstance(obj, Tag):
-                self.dji.add_tag(obj.serialize())
-        # Next we add the links:
-        for key in list(self.import_cache.keys()):
-            obj = self.import_cache[key]
-            if isinstance(obj, Person):
-                self.dji.add_person_detail(obj.serialize())
-            elif isinstance(obj, Family):
-                self.dji.add_family_detail(obj.serialize())
-            elif isinstance(obj, Event):
-                self.dji.add_event_detail(obj.serialize())
-            elif isinstance(obj, Place):
-                self.dji.add_place_detail(obj.serialize())
-            elif isinstance(obj, Repository):
-                self.dji.add_repository_detail(obj.serialize())
-            elif isinstance(obj, Citation):
-                self.dji.add_citation_detail(obj.serialize())
-            elif isinstance(obj, Source):
-                self.dji.add_source_detail(obj.serialize())
-            elif isinstance(obj, Note):
-                self.dji.add_note_detail(obj.serialize())
-            elif isinstance(obj, MediaObject):
-                self.dji.add_media_detail(obj.serialize())
-            elif isinstance(obj, Tag):
-                self.dji.add_tag_detail(obj.serialize())
-        self.use_import_cache = False
-        self.import_cache = {}
-        self.dji.update_publics()
-
-    def transaction_commit(self, txn):
-        pass
-
-    def request_rebuild(self):
-        # caches are ok, but let's compute public's
-        self.dji.update_publics()
-        self.emit('person-rebuild')
-        self.emit('family-rebuild')
-        self.emit('place-rebuild')
-        self.emit('source-rebuild')
-        self.emit('citation-rebuild')
-        self.emit('media-rebuild')
-        self.emit('event-rebuild')
-        self.emit('repository-rebuild')
-        self.emit('note-rebuild')
-        self.emit('tag-rebuild')
-
-    def get_undodb(self):
-        return None
-
-    def transaction_abort(self, txn):
-        pass
-
-    @staticmethod
-    def _validated_id_prefix(val, default):
-        if isinstance(val, str) and val:
-            try:
-                str_ = val % 1
-            except TypeError:           # missing conversion specifier
-                prefix_var = val + "%d"
-            except ValueError:          # incomplete format
-                prefix_var = default+"%04d"
-            else:
-                prefix_var = val        # OK as given
-        else:
-            prefix_var = default+"%04d" # not a string or empty string
-        return prefix_var
-
-    @staticmethod
-    def __id2user_format(id_pattern):
-        """
-        Return a method that accepts a Gramps ID and adjusts it to the users
-        format.
-        """
-        pattern_match = re.match(r"(.*)%[0 ](\d+)[diu]$", id_pattern)
-        if pattern_match:
-            str_prefix = pattern_match.group(1)
-            nr_width = pattern_match.group(2)
-            def closure_func(gramps_id):
-                if gramps_id and gramps_id.startswith(str_prefix):
-                    id_number = gramps_id[len(str_prefix):]
-                    if id_number.isdigit():
-                        id_value = int(id_number, 10)
-                        #if len(str(id_value)) > nr_width:
-                        #    # The ID to be imported is too large to fit in the
-                        #    # users format. For now just create a new ID,
-                        #    # because that is also what happens with IDs that
-                        #    # are identical to IDs already in the database. If
-                        #    # the problem of colliding import and already
-                        #    # present IDs is solved the code here also needs
-                        #    # some solution.
-                        #    gramps_id = id_pattern % 1
-                        #else:
-                        gramps_id = id_pattern % id_value
-                return gramps_id
-        else:
-            def closure_func(gramps_id):
-                return gramps_id
-        return closure_func
-
-    def set_person_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Person ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as I%d or I%04d.
-        """
-        self.person_prefix = self._validated_id_prefix(val, "I")
-        self.id2user_format = self.__id2user_format(self.person_prefix)
-
-    def set_citation_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Citation ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as C%d or C%04d.
-        """
-        self.citation_prefix = self._validated_id_prefix(val, "C")
-        self.cid2user_format = self.__id2user_format(self.citation_prefix)
-            
-    def set_source_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Source ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as S%d or S%04d.
-        """
-        self.source_prefix = self._validated_id_prefix(val, "S")
-        self.sid2user_format = self.__id2user_format(self.source_prefix)
-            
-    def set_object_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS MediaObject ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as O%d or O%04d.
-        """
-        self.mediaobject_prefix = self._validated_id_prefix(val, "O")
-        self.oid2user_format = self.__id2user_format(self.mediaobject_prefix)
-
-    def set_place_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Place ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as P%d or P%04d.
-        """
-        self.place_prefix = self._validated_id_prefix(val, "P")
-        self.pid2user_format = self.__id2user_format(self.place_prefix)
-
-    def set_family_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Family ID values. The string is
-        expected to be in the form of a simple text string, or in a format
-        that contains a C/Python style format string using %d, such as F%d
-        or F%04d.
-        """
-        self.family_prefix = self._validated_id_prefix(val, "F")
-        self.fid2user_format = self.__id2user_format(self.family_prefix)
-
-    def set_event_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Event ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as E%d or E%04d.
-        """
-        self.event_prefix = self._validated_id_prefix(val, "E")
-        self.eid2user_format = self.__id2user_format(self.event_prefix)
-
-    def set_repository_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Repository ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as R%d or R%04d.
-        """
-        self.repository_prefix = self._validated_id_prefix(val, "R")
-        self.rid2user_format = self.__id2user_format(self.repository_prefix)
-
-    def set_note_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Note ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as N%d or N%04d.
-        """
-        self.note_prefix = self._validated_id_prefix(val, "N")
-        self.nid2user_format = self.__id2user_format(self.note_prefix)
-
-    def __find_next_gramps_id(self, prefix, map_index, trans):
-        """
-        Helper function for find_next_<object>_gramps_id methods
-        """
-        index = prefix % map_index
-        while trans.get(str(index), txn=self.txn) is not None:
-            map_index += 1
-            index = prefix % map_index
-        map_index += 1
-        return (map_index, index)
-        
-    def find_next_person_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Person object based off the 
-        person ID prefix.
-        """
-        self.pmap_index, gid = self.__find_next_gramps_id(self.person_prefix,
-                                          self.pmap_index, self.id_trans)
-        return gid
-
-    def find_next_place_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Place object based off the 
-        place ID prefix.
-        """
-        self.lmap_index, gid = self.__find_next_gramps_id(self.place_prefix,
-                                          self.lmap_index, self.pid_trans)
-        return gid
-
-    def find_next_event_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Event object based off the 
-        event ID prefix.
-        """
-        self.emap_index, gid = self.__find_next_gramps_id(self.event_prefix,
-                                          self.emap_index, self.eid_trans)
-        return gid
-
-    def find_next_object_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a MediaObject object based
-        off the media object ID prefix.
-        """
-        self.omap_index, gid = self.__find_next_gramps_id(self.mediaobject_prefix,
-                                          self.omap_index, self.oid_trans)
-        return gid
-
-    def find_next_citation_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Citation object based off the 
-        citation ID prefix.
-        """
-        self.cmap_index, gid = self.__find_next_gramps_id(self.citation_prefix,
-                                          self.cmap_index, self.cid_trans)
-        return gid
-
-    def find_next_source_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Source object based off the 
-        source ID prefix.
-        """
-        self.smap_index, gid = self.__find_next_gramps_id(self.source_prefix,
-                                          self.smap_index, self.sid_trans)
-        return gid
-
-    def find_next_family_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Family object based off the 
-        family ID prefix.
-        """
-        self.fmap_index, gid = self.__find_next_gramps_id(self.family_prefix,
-                                          self.fmap_index, self.fid_trans)
-        return gid
-
-    def find_next_repository_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Respository object based 
-        off the repository ID prefix.
-        """
-        self.rmap_index, gid = self.__find_next_gramps_id(self.repository_prefix,
-                                          self.rmap_index, self.rid_trans)
-        return gid
-
-    def find_next_note_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Note object based off the 
-        note ID prefix.
-        """
-        self.nmap_index, gid = self.__find_next_gramps_id(self.note_prefix,
-                                          self.nmap_index, self.nid_trans)
-        return gid
-
-    def get_mediapath(self):
-        return None
-
-    def get_name_group_keys(self):
-        return []
-
-    def get_name_group_mapping(self, key):
-        return None
-
-    def get_researcher(self):
-        obj = Researcher()
-        return obj
-
-    def get_tag_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Tag.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Tag.all()]
-
-    def get_person_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Person.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Person.all()]
-
-    def get_family_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Family.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Family.all()]
-
-    def get_event_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Event.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Event.all()]
-
-    def get_citation_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Citation.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Citation.all()]
-
-    def get_source_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Source.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Source.all()]
-
-    def get_place_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Place.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Place.all()]
-
-    def get_repository_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Repository.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Repository.all()]
-
-    def get_media_object_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Media.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Media.all()]
-
-    def get_note_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Note.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Note.all()]
-
-    def get_media_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            media = self.dji.Media.get(handle=handle)
-        except:
-            return None
-        return self.make_media(media)
-
-    def get_event_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            event = self.dji.Event.get(handle=handle)
-        except:
-            return None
-        return self.make_event(event)
-
-    def get_family_from_handle(self, handle): 
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            family = self.dji.Family.get(handle=handle)
-        except:
-            return None
-        return self.make_family(family)
-
-    def get_repository_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            repository = self.dji.Repository.get(handle=handle)
-        except:
-            return None
-        return self.make_repository(repository)
-
-    def get_person_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            person = self.dji.Person.get(handle=handle)
-        except:
-            return None
-        return self.make_person(person)
-
-    def get_tag_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            tag = self.dji.Tag.get(handle=handle)
-        except:
-            return None
-        return self.make_tag(tag)
-
-    def make_repository(self, repository):
-        if self.use_db_cache and repository.cache:
-            data = repository.from_cache()
-        else:
-            data = self.dji.get_repository(repository)
-        return Repository.create(data)
-
-    def make_citation(self, citation):
-        if self.use_db_cache and citation.cache:
-            data = citation.from_cache()
-        else:
-            data = self.dji.get_citation(citation)
-        return Citation.create(data)
-
-    def make_source(self, source):
-        if self.use_db_cache and source.cache:
-            data = source.from_cache()
-        else:
-            data = self.dji.get_source(source)
-        return Source.create(data)
-
-    def make_family(self, family):
-        if self.use_db_cache and family.cache:
-            data = family.from_cache()
-        else:
-            data = self.dji.get_family(family)
-        return Family.create(data)
-
-    def make_person(self, person):
-        if self.use_db_cache and person.cache:
-            data = person.from_cache()
-        else:
-            data = self.dji.get_person(person)
-        return Person.create(data)
-
-    def make_event(self, event):
-        if self.use_db_cache and event.cache:
-            data = event.from_cache()
-        else:
-            data = self.dji.get_event(event)
-        return Event.create(data)
-
-    def make_note(self, note):
-        if self.use_db_cache and note.cache:
-            data = note.from_cache()
-        else:
-            data = self.dji.get_note(note)
-        return Note.create(data)
-
-    def make_tag(self, tag):
-        data = self.dji.get_tag(tag)
-        return Tag.create(data)
-
-    def make_place(self, place):
-        if self.use_db_cache and place.cache:
-            data = place.from_cache()
-        else:
-            data = self.dji.get_place(place)
-        return Place.create(data)
-
-    def make_media(self, media):
-        if self.use_db_cache and media.cache:
-            data = media.from_cache()
-        else:
-            data = self.dji.get_media(media)
-        return MediaObject.create(data)
-
-    def get_place_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            place = self.dji.Place.get(handle=handle)
-        except:
-            return None
-        return self.make_place(place)
-
-    def get_citation_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            citation = self.dji.Citation.get(handle=handle)
-        except:
-            return None
-        return self.make_citation(citation)
-
-    def get_source_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            source = self.dji.Source.get(handle=handle)
-        except:
-            return None
-        return self.make_source(source)
-
-    def get_note_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            note = self.dji.Note.get(handle=handle)
-        except:
-            return None
-        return self.make_note(note)
-
-    def get_object_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            media = self.dji.Media.get(handle=handle)
-        except:
-            return None
-        return self.make_media(media)
-
-    def get_default_person(self):
-        people = self.dji.Person.all()
-        if people.count() > 0:
-            return self.make_person(people[0])
-        return None
-
-    def iter_people(self):
-        return (self.get_person_from_handle(person.handle) 
-                for person in self.dji.Person.all())
-
-    def iter_person_handles(self):
-        return (person.handle for person in self.dji.Person.all())
-
-    def iter_families(self):
-        return (self.get_family_from_handle(family.handle) 
-                for family in self.dji.Family.all())
-
-    def iter_family_handles(self):
-        return (family.handle for family in self.dji.Family.all())
-
-    def iter_notes(self):
-        return (self.get_note_from_handle(note.handle) 
-                for note in self.dji.Note.all())
-
-    def iter_note_handles(self):
-        return (note.handle for note in self.dji.Note.all())
-
-    def iter_events(self):
-        return (self.get_event_from_handle(event.handle) 
-                for event in self.dji.Event.all())
-
-    def iter_event_handles(self):
-        return (event.handle for event in self.dji.Event.all())
-
-    def iter_places(self):
-        return (self.get_place_from_handle(place.handle) 
-                for place in self.dji.Place.all())
-
-    def iter_place_handles(self):
-        return (place.handle for place in self.dji.Place.all())
-
-    def iter_repositories(self):
-        return (self.get_repository_from_handle(repository.handle) 
-                for repository in self.dji.Repository.all())
-
-    def iter_repository_handles(self):
-        return (repository.handle for repository in self.dji.Repository.all())
-
-    def iter_sources(self):
-        return (self.get_source_from_handle(source.handle) 
-                for source in self.dji.Source.all())
-
-    def iter_source_handles(self):
-        return (source.handle for source in self.dji.Source.all())
-
-    def iter_citations(self):
-        return (self.get_citation_from_handle(citation.handle) 
-                for citation in self.dji.Citation.all())
-
-    def iter_citation_handles(self):
-        return (citation.handle for citation in self.dji.Citation.all())
-
-    def iter_tags(self):
-        return (self.get_tag_from_handle(tag.handle) 
-                for tag in self.dji.Tag.all())
-
-    def iter_tag_handles(self):
-        return (tag.handle for tag in self.dji.Tag.all())
-
-    def iter_media_objects(self):
-        return (self.get_media_from_handle(media.handle) 
-                for media in self.dji.Media.all())
-
-    def get_tag_from_name(self, name):
-        try:
-            tag = self.dji.Tag.filter(name=name)
-            return self.make_tag(tag[0])
-        except:
-            return None
-
-    def get_person_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        match_list = self.dji.Person.filter(gramps_id=gramps_id)
-        if match_list.count() > 0:
-            return self.make_person(match_list[0])
-        else:
-            return None
-
-    def get_family_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        try:
-            family = self.dji.Family.get(gramps_id=gramps_id)
-        except:
-            return None
-        return self.make_family(family)
-
-    def get_source_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        match_list = self.dji.Source.filter(gramps_id=gramps_id)
-        if match_list.count() > 0:
-            return self.make_source(match_list[0])
-        else:
-            return None
-
-    def get_citation_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        match_list = self.dji.Citation.filter(gramps_id=gramps_id)
-        if match_list.count() > 0:
-            return self.make_citation(match_list[0])
-        else:
-            return None
-
-    def get_event_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        match_list = self.dji.Event.filter(gramps_id=gramps_id)
-        if match_list.count() > 0:
-            return self.make_event(match_list[0])
-        else:
-            return None
-
-    def get_object_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        match_list = self.dji.Media.filter(gramps_id=gramps_id)
-        if match_list.count() > 0:
-            return self.make_media(match_list[0])
-        else:
-            return None
-
-    def get_place_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        match_list = self.dji.Place.filter(gramps_id=gramps_id)
-        if match_list.count() > 0:
-            return self.make_place(match_list[0])
-        else:
-            return None
-
-    def get_repository_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        match_list = self.dji.Repsoitory.filter(gramps_id=gramps_id)
-        if match_list.count() > 0:
-            return self.make_repository(match_list[0])
-        else:
-            return None
-
-    def get_note_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        match_list = self.dji.Note.filter(gramps_id=gramps_id)
-        if match_list.count() > 0:
-            return self.make_note(match_list[0])
-        else:
-            return None
-
-    def get_number_of_people(self):
-        return self.dji.Person.count()
-
-    def get_number_of_events(self):
-        return self.dji.Event.count()
-
-    def get_number_of_places(self):
-        return self.dji.Place.count()
-
-    def get_number_of_tags(self):
-        return self.dji.Tag.count()
-
-    def get_number_of_families(self):
-        return self.dji.Family.count()
-
-    def get_number_of_notes(self):
-        return self.dji.Note.count()
-
-    def get_number_of_citations(self):
-        return self.dji.Citation.count()
-
-    def get_number_of_sources(self):
-        return self.dji.Source.count()
-
-    def get_number_of_media_objects(self):
-        return self.dji.Media.count()
-
-    def get_number_of_repositories(self):
-        return self.dji.Repository.count()
-
-    def get_place_cursor(self):
-        return Cursor(self.dji.Place, self.get_raw_place_data)
-
-    def get_person_cursor(self):
-        return Cursor(self.dji.Person, self.get_raw_person_data)
-
-    def get_family_cursor(self):
-        return Cursor(self.dji.Family, self.get_raw_family_data)
-
-    def get_event_cursor(self):
-        return Cursor(self.dji.Event, self.get_raw_event_data)
-
-    def get_citation_cursor(self):
-        return Cursor(self.dji.Citation, self.get_raw_citation_data)
-
-    def get_source_cursor(self):
-        return Cursor(self.dji.Source, self.get_raw_source_data)
-
-    def get_note_cursor(self):
-        return Cursor(self.dji.Note, self.get_raw_note_data)
-
-    def get_tag_cursor(self):
-        return Cursor(self.dji.Tag, self.get_raw_tag_data)
-
-    def get_repository_cursor(self):
-        return Cursor(self.dji.Repository, self.get_raw_repository_data)
-
-    def get_media_cursor(self):
-        return Cursor(self.dji.Media, self.get_raw_object_data)
-
-    def has_gramps_id(self, obj_key, gramps_id):
-        key2table = {
-            PERSON_KEY:     self.dji.Person, 
-            FAMILY_KEY:     self.dji.Family, 
-            SOURCE_KEY:     self.dji.Source, 
-            CITATION_KEY:   self.dji.Citation, 
-            EVENT_KEY:      self.dji.Event, 
-            MEDIA_KEY:      self.dji.Media, 
-            PLACE_KEY:      self.dji.Place, 
-            REPOSITORY_KEY: self.dji.Repository, 
-            NOTE_KEY:       self.dji.Note, 
-            }
-        table = key2table[obj_key]
-        return table.filter(gramps_id=gramps_id).count() > 0
-
-    def has_person_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Person.filter(handle=handle).count() == 1
-
-    def has_family_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Family.filter(handle=handle).count() == 1
-
-    def has_citation_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Citation.filter(handle=handle).count() == 1
-
-    def has_source_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Source.filter(handle=handle).count() == 1
-
-    def has_repository_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Repository.filter(handle=handle).count() == 1
-
-    def has_note_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Note.filter(handle=handle).count() == 1
-
-    def has_place_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Place.filter(handle=handle).count() == 1
-
-    def has_event_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Event.filter(handle=handle).count() == 1
-
-    def has_tag_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Tag.filter(handle=handle).count() == 1
-
-    def has_object_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Media.filter(handle=handle).count() == 1
-
-    def has_name_group_key(self, key):
-        # FIXME:
-        return False
-
-    def set_name_group_mapping(self, key, value):
-        # FIXME:
-        pass
-
-    def set_default_person_handle(self, handle):
-        pass
-
-    def set_mediapath(self, mediapath):
-        pass
-
-    def get_raw_person_data(self, handle):
-        try:
-            return self.dji.get_person(self.dji.Person.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_family_data(self, handle):
-        try:
-            return self.dji.get_family(self.dji.Family.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_citation_data(self, handle):
-        try:
-            return self.dji.get_citation(self.dji.Citation.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_source_data(self, handle):
-        try:
-            return self.dji.get_source(self.dji.Source.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_repository_data(self, handle):
-        try:
-            return self.dji.get_repository(self.dji.Repository.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_note_data(self, handle):
-        try:
-            return self.dji.get_note(self.dji.Note.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_place_data(self, handle):
-        try:
-            return self.dji.get_place(self.dji.Place.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_object_data(self, handle):
-        try:
-            return self.dji.get_media(self.dji.Media.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_tag_data(self, handle):
-        try:
-            return self.dji.get_tag(self.dji.Tag.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_event_data(self, handle):
-        try:
-            return self.dji.get_event(self.dji.Event.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def add_person(self, person, trans, set_gid=True):
-        if not person.handle:
-            person.handle = create_id()
-        if not person.gramps_id and set_gid:
-            person.gramps_id = self.find_next_person_gramps_id()
-        self.commit_person(person, trans)
-        self.emit("person-add", ([person.handle],))
-        return person.handle
-
-    def add_family(self, family, trans, set_gid=True):
-        if not family.handle:
-            family.handle = create_id()
-        if not family.gramps_id and set_gid:
-            family.gramps_id = self.find_next_family_gramps_id()
-        self.commit_family(family, trans)
-        self.emit("family-add", ([family.handle],))
-        return family.handle
-
-    def add_citation(self, citation, trans, set_gid=True):
-        if not citation.handle:
-            citation.handle = create_id()
-        if not citation.gramps_id and set_gid:
-            citation.gramps_id = self.find_next_citation_gramps_id()
-        self.commit_citation(citation, trans)
-        self.emit("citation-add", ([citation.handle],))
-        return citation.handle
-
-    def add_source(self, source, trans, set_gid=True):
-        if not source.handle:
-            source.handle = create_id()
-        if not source.gramps_id and set_gid:
-            source.gramps_id = self.find_next_source_gramps_id()
-        self.commit_source(source, trans)
-        self.emit("source-add", ([source.handle],))
-        return source.handle
-
-    def add_repository(self, repository, trans, set_gid=True):
-        if not repository.handle:
-            repository.handle = create_id()
-        if not repository.gramps_id and set_gid:
-            repository.gramps_id = self.find_next_repository_gramps_id()
-        self.commit_repository(repository, trans)
-        self.emit("repository-add", ([repository.handle],))
-        return repository.handle
-
-    def add_note(self, note, trans, set_gid=True):
-        if not note.handle:
-            note.handle = create_id()
-        if not note.gramps_id and set_gid:
-            note.gramps_id = self.find_next_note_gramps_id()
-        self.commit_note(note, trans)
-        self.emit("note-add", ([note.handle],))
-        return note.handle
-
-    def add_place(self, place, trans, set_gid=True):
-        if not place.handle:
-            place.handle = create_id()
-        if not place.gramps_id and set_gid:
-            place.gramps_id = self.find_next_place_gramps_id()
-        self.commit_place(place, trans)
-        return place.handle
-
-    def add_event(self, event, trans, set_gid=True):
-        if not event.handle:
-            event.handle = create_id()
-        if not event.gramps_id and set_gid:
-            event.gramps_id = self.find_next_event_gramps_id()
-        self.commit_event(event, trans)
-        return event.handle
-
-    def add_tag(self, tag, trans):
-        if not tag.handle:
-            tag.handle = create_id()
-        self.commit_event(tag, trans)
-        return tag.handle
-
-    def add_object(self, obj, transaction, set_gid=True):
-        """
-        Add a MediaObject to the database, assigning internal IDs if they have
-        not already been defined.
-        
-        If not set_gid, then gramps_id is not set.
-        """
-        if not obj.handle:
-            obj.handle = create_id()
-        if not obj.gramps_id and set_gid:
-            obj.gramps_id = self.find_next_object_gramps_id()
-        self.commit_media_object(obj, transaction)
-        return obj.handle
-
-    def commit_person(self, person, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[person.handle] = person
-        else:
-            raw = person.serialize()
-            items = self.dji.Person.filter(handle=person.handle)
-            if items.count() > 0:
-                # Hack, for the moment: delete and re-add
-                items[0].delete()
-            self.dji.add_person(person.serialize())
-            self.dji.add_person_detail(person.serialize())
-            if items.count() > 0:
-                self.emit("person-update", ([person.handle],))
-            else:
-                self.emit("person-add", ([person.handle],))
-
-    def commit_family(self, family, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[family.handle] = family
-        else:
-            raw = family.serialize()
-            items = self.dji.Family.filter(handle=family.handle)
-            if items.count() > 0:
-                items[0].delete()
-            self.dji.add_family(family.serialize())
-            self.dji.add_family_detail(family.serialize())
-            if items.count() > 0:
-                self.emit("family-update", ([family.handle],))
-            else:
-                self.emit("family-add", ([family.handle],))
-
-    def commit_citation(self, citation, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[citation.handle] = citation
-        else:
-            raw = citation.serialize()
-            items = self.dji.Citation.filter(handle=citation.handle)
-            if items.count() > 0:
-                items[0].delete()
-            self.dji.add_citation(citation.serialize())
-            self.dji.add_citation_detail(citation.serialize())
-            if items.count() > 0:
-                self.emit("citation-update", ([citation.handle],))
-            else:
-                self.emit("citation-add", ([citation.handle],))
-
-    def commit_source(self, source, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[source.handle] = source
-        else:
-            raw = source.serialize()
-            items = self.dji.Source.filter(handle=source.handle)
-            if items.count() > 0:
-                items[0].delete()
-            self.dji.add_source(source.serialize())
-            self.dji.add_source_detail(source.serialize())
-            if items.count() > 0:
-                self.emit("source-update", ([source.handle],))
-            else:
-                self.emit("source-add", ([source.handle],))
-
-    def commit_repository(self, repository, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[repository.handle] = repository
-        else:
-            raw = repository.serialize()
-            items = self.dji.Repository.filter(handle=repository.handle)
-            if items.count() > 0:
-                items[0].delete()
-            self.dji.add_repository(repository.serialize())
-            self.dji.add_repository_detail(repository.serialize())
-            if items.count() > 0:
-                self.emit("repository-update", ([repository.handle],))
-            else:
-                self.emit("repository-add", ([repository.handle],))
-
-    def commit_note(self, note, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[note.handle] = note
-        else:
-            raw = note.serialize()
-            items = self.dji.Note.filter(handle=note.handle)
-            if items.count() > 0:
-                items[0].delete()
-            self.dji.add_note(note.serialize())
-            self.dji.add_note_detail(note.serialize())
-            if items.count() > 0:
-                self.emit("note-update", ([note.handle],))
-            else:
-                self.emit("note-add", ([note.handle],))
-
-    def commit_place(self, place, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[place.handle] = place
-        else:
-            raw = place.serialize()
-            items = self.dji.Place.filter(handle=place.handle)
-            if items.count() > 0:
-                items[0].delete()
-            self.dji.add_place(place.serialize())
-            self.dji.add_place_detail(place.serialize())
-            if items.count() > 0:
-                self.emit("place-update", ([place.handle],))
-            else:
-                self.emit("place-add", ([place.handle],))
-
-    def commit_event(self, event, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[event.handle] = event
-        else:
-            raw = event.serialize()
-            items = self.dji.Event.filter(handle=event.handle)
-            if items.count() > 0:
-                items[0].delete()
-            self.dji.add_event(event.serialize())
-            self.dji.add_event_detail(event.serialize())
-            if items.count() > 0:
-                self.emit("event-update", ([event.handle],))
-            else:
-                self.emit("event-add", ([event.handle],))
-
-    def commit_tag(self, tag, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[tag.handle] = tag
-        else:
-            raw = tag.serialize()
-            items = self.dji.Tag.filter(handle=tag.handle)
-            if items.count() > 0:
-                items[0].delete()
-            self.dji.add_tag(tag.serialize())
-            self.dji.add_tag_detail(tag.serialize())
-            if items.count() > 0:
-                self.emit("tag-update", ([tag.handle],))
-            else:
-                self.emit("tag-add", ([tag.handle],))
-
-    def commit_media_object(self, media, transaction, change_time=None):
-        """
-        Commit the specified MediaObject to the database, storing the changes
-        as part of the transaction.
-        """
-        if self.use_import_cache:
-            self.import_cache[obj.handle] = media
-        else:
-            raw = media.serialize()
-            items = self.dji.Media.filter(handle=media.handle)
-            if items.count() > 0:
-                items[0].delete()
-            self.dji.add_media(media.serialize())
-            self.dji.add_media_detail(media.serialize())
-            if items.count() > 0:
-                self.emit("media-update", ([media.handle],))
-            else:
-                self.emit("media-add", ([media.handle],))
-
-    def get_gramps_ids(self, obj_key):
-        key2table = {
-            PERSON_KEY:     self.id_trans, 
-            FAMILY_KEY:     self.fid_trans, 
-            CITATION_KEY:   self.cid_trans, 
-            SOURCE_KEY:     self.sid_trans, 
-            EVENT_KEY:      self.eid_trans, 
-            MEDIA_KEY:      self.oid_trans, 
-            PLACE_KEY:      self.pid_trans, 
-            REPOSITORY_KEY: self.rid_trans, 
-            NOTE_KEY:       self.nid_trans, 
-            }
-
-        table = key2table[obj_key]
-        return list(table.keys())
-
-    def transaction_begin(self, transaction):
-        return 
-
-    def set_researcher(self, owner):
-        pass
-
-    def copy_from_db(self, db):
-        """
-        A (possibily) implementation-specific method to get data from
-        db into this database.
-        """
-        # First we add the primary objects:
-        for key in db._tables.keys():
-            cursor = db._tables[key]["cursor_func"]
-            for (handle, data) in cursor():
-                if key == "Person":
-                    self.dji.add_person(data)
-                elif key == "Family":
-                    self.dji.add_family(data)
-                elif key == "Event":
-                    self.dji.add_event(data)
-                elif key == "Place":
-                    self.dji.add_place(data)
-                elif key == "Repository":
-                    self.dji.add_repository(data)
-                elif key == "Citation":
-                    self.dji.add_citation(data)
-                elif key == "Source":
-                    self.dji.add_source(data)
-                elif key == "Note":
-                    self.dji.add_note(data)
-                elif key == "Media":
-                    self.dji.add_media(data)
-                elif key == "Tag":
-                    self.dji.add_tag(data)
-        for key in db._tables.keys():
-            cursor = db._tables[key]["cursor_func"]
-            for (handle, data) in cursor():
-                if key == "Person":
-                    self.dji.add_person_detail(data)
-                elif key == "Family":
-                    self.dji.add_family_detail(data)
-                elif key == "Event":
-                    self.dji.add_event_detail(data)
-                elif key == "Place":
-                    self.dji.add_place_detail(data)
-                elif key == "Repository":
-                    self.dji.add_repository_detail(data)
-                elif key == "Citation":
-                    self.dji.add_citation_detail(data)
-                elif key == "Source":
-                    self.dji.add_source_detail(data)
-                elif key == "Note":
-                    self.dji.add_note_detail(data)
-                elif key == "Media":
-                    self.dji.add_media_detail(data)
-                elif key == "Tag":
-                    self.dji.add_tag_detail(data)
-            # Next we add the links:
-        self.dji.update_publics()
-
-    def get_from_name_and_handle(self, table_name, handle):
-        """
-        Returns a gen.lib object (or None) given table_name and
-        handle.
-
-        Examples:
-
-        >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008")
-        >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23")
-        """
-        if table_name in self._tables:
-            return self._tables[table_name]["handle_func"](handle)
-        return None
-
-    def is_empty(self):
-        """
-        Is the database empty?
-        """
-        return (self.get_number_of_people() == 0 and
-                self.get_number_of_events() == 0 and
-                self.get_number_of_places() == 0 and
-                self.get_number_of_tags() == 0 and
-                self.get_number_of_families() == 0 and
-                self.get_number_of_notes() == 0 and
-                self.get_number_of_citations() == 0 and
-                self.get_number_of_sources() == 0 and
-                self.get_number_of_media_objects() == 0 and
-                self.get_number_of_repositories() == 0)
-                
-    __callback_map = {}
-
-    def set_prefixes(self, person, media, family, source, citation,
-                     place, event, repository, note):
-        self.set_person_id_prefix(person)
-        self.set_object_id_prefix(media)
-        self.set_family_id_prefix(family)
-        self.set_source_id_prefix(source)
-        self.set_citation_id_prefix(citation)
-        self.set_place_id_prefix(place)
-        self.set_event_id_prefix(event)
-        self.set_repository_id_prefix(repository)
-        self.set_note_id_prefix(note)
-
-    def has_changed(self):
-        return False
-
-    def find_backlink_handles(self, handle, include_classes=None):
-        ## FIXME: figure out how to get objects that refer
-        ## to this handle
-        return []
-
-    def get_note_bookmarks(self):
-        return self.note_bookmarks
-
-    def get_media_bookmarks(self):
-        return self.media_bookmarks
-
-    def get_repo_bookmarks(self):
-        return self.repo_bookmarks
-
-    def get_citation_bookmarks(self):
-        return self.citation_bookmarks
-
-    def get_source_bookmarks(self):
-        return self.source_bookmarks
-
-    def get_place_bookmarks(self):
-        return self.place_bookmarks
-
-    def get_event_bookmarks(self):
-        return self.event_bookmarks
-
-    def get_bookmarks(self):
-        return self.bookmarks
-
-    def get_family_bookmarks(self):
-        return self.family_bookmarks
-
-    def get_save_path(self):
-        return "/tmp/"
-
-    ## Get types:
-    def get_event_attribute_types(self):
-        """
-        Return a list of all Attribute types assocated with Event instances
-        in the database.
-        """
-        return list(self.event_attributes)
-
-    def get_event_types(self):
-        """
-        Return a list of all event types in the database.
-        """
-        return list(self.event_names)
-
-    def get_person_event_types(self):
-        """
-        Deprecated:  Use get_event_types
-        """
-        return list(self.event_names)
-
-    def get_person_attribute_types(self):
-        """
-        Return a list of all Attribute types assocated with Person instances 
-        in the database.
-        """
-        return list(self.individual_attributes)
-
-    def get_family_attribute_types(self):
-        """
-        Return a list of all Attribute types assocated with Family instances 
-        in the database.
-        """
-        return list(self.family_attributes)
-
-    def get_family_event_types(self):
-        """
-        Deprecated:  Use get_event_types
-        """
-        return list(self.event_names)
-
-    def get_media_attribute_types(self):
-        """
-        Return a list of all Attribute types assocated with Media and MediaRef 
-        instances in the database.
-        """
-        return list(self.media_attributes)
-
-    def get_family_relation_types(self):
-        """
-        Return a list of all relationship types assocated with Family
-        instances in the database.
-        """
-        return list(self.family_rel_types)
-
-    def get_child_reference_types(self):
-        """
-        Return a list of all child reference types assocated with Family
-        instances in the database.
-        """
-        return list(self.child_ref_types)
-
-    def get_event_roles(self):
-        """
-        Return a list of all custom event role names assocated with Event
-        instances in the database.
-        """
-        return list(self.event_role_names)
-
-    def get_name_types(self):
-        """
-        Return a list of all custom names types assocated with Person
-        instances in the database.
-        """
-        return list(self.name_types)
-
-    def get_origin_types(self):
-        """
-        Return a list of all custom origin types assocated with Person/Surname
-        instances in the database.
-        """
-        return list(self.origin_types)
-
-    def get_repository_types(self):
-        """
-        Return a list of all custom repository types assocated with Repository 
-        instances in the database.
-        """
-        return list(self.repository_types)
-
-    def get_note_types(self):
-        """
-        Return a list of all custom note types assocated with Note instances 
-        in the database.
-        """
-        return list(self.note_types)
-
-    def get_source_attribute_types(self):
-        """
-        Return a list of all Attribute types assocated with Source/Citation
-        instances in the database.
-        """
-        return list(self.source_attributes)
-
-    def get_source_media_types(self):
-        """
-        Return a list of all custom source media types assocated with Source 
-        instances in the database.
-        """
-        return list(self.source_media_types)
-
-    def get_url_types(self):
-        """
-        Return a list of all custom names types assocated with Url instances 
-        in the database.
-        """
-        return list(self.url_types)
-
-    def get_place_types(self):
-        """
-        Return a list of all custom place types assocated with Place instances
-        in the database.
-        """
-        return list(self.place_types)
-
-    def get_default_handle(self):
-        people = self.dji.Person.all()
-        if people.count() > 0:
-            return people[0].handle
-        return None
-
-    def close(self):
-        pass
-
-    def get_surname_list(self):
-        return []
-
-    def is_open(self):
-        return True
-
-    def get_table_names(self):
-        """Return a list of valid table names."""
-        return list(self._tables.keys())
-
-    def find_initial_person(self):
-        return self.get_default_person()
-
-    # Removals:
-    def remove_person(self, handle, txn):
-        self.dji.Person.filter(handle=handle)[0].delete()
-        self.emit("person-delete", ([handle],))
-
-    def remove_source(self, handle, transaction):
-        self.dji.Source.filter(handle=handle)[0].delete()
-        self.emit("source-delete", ([handle],))
-
-    def remove_citation(self, handle, transaction):
-        self.dji.Citation.filter(handle=handle)[0].delete()
-        self.emit("citation-delete", ([handle],))
-
-    def remove_event(self, handle, transaction):
-        self.dji.Event.filter(handle=handle)[0].delete()
-        self.emit("event-delete", ([handle],))
-
-    def remove_object(self, handle, transaction):
-        self.dji.Media.filter(handle=handle)[0].delete()
-        self.emit("media-delete", ([handle],))
-
-    def remove_place(self, handle, transaction):
-        self.dji.Place.filter(handle=handle)[0].delete()
-        self.emit("place-delete", ([handle],))
-
-    def remove_family(self, handle, transaction):
-        self.dji.Family.filter(handle=handle)[0].delete()
-        self.emit("family-delete", ([handle],))
-
-    def remove_repository(self, handle, transaction):
-        self.dji.Repository.filter(handle=handle)[0].delete()
-        self.emit("repository-delete", ([handle],))
-
-    def remove_note(self, handle, transaction):
-        self.dji.Note.filter(handle=handle)[0].delete()
-        self.emit("note-delete", ([handle],))
-
-    def remove_tag(self, handle, transaction):
-        self.dji.Tag.filter(handle=handle)[0].delete()
-        self.emit("tag-delete", ([handle],))
-
-    def remove_from_surname_list(self, person):
-        ## FIXME
-        pass
-    
-    def get_dbname(self):
-        return "Django Database"
-
-    ## missing
-
-    def find_place_child_handles(self, handle):
-        pass
-
-    def get_cursor(self, table, txn=None, update=False, commit=False):
-        pass
-
-    def get_from_name_and_handle(self, table_name, handle):
-        """
-        Returns a gen.lib object (or None) given table_name and
-        handle.
-
-        Examples:
-
-        >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008")
-        >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23")
-        """
-        if table_name in self._tables:
-            return self._tables[table_name]["handle_func"](handle)
-        return None
-
-    def get_number_of_records(self, table):
-        pass
-
-    def get_place_parent_cursor(self):
-        pass
-
-    def get_place_tree_cursor(self):
-        pass
-
-    def get_table_metadata(self, table_name):
-        """Return the metadata for a valid table name."""
-        if table_name in self._tables:
-            return self._tables[table_name]
-        return None
-
-    def get_transaction_class(self):
-        pass
-
-    def undo(self, update_history=True):
-        # FIXME:
-        return self.undodb.undo(update_history)
-
-    def redo(self, update_history=True):
-        # FIXME:
-        return self.undodb.redo(update_history)
-
-    def backup(self):
-        pass
-
-    def restore(self):
-        pass
diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 936d8f534..1542f4c93 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -130,27 +130,33 @@ class Cursor(object):
     def __init__(self, map, func):
         self.map = map
         self.func = func
+        self._iter = self.__iter__()
     def __enter__(self):
         return self
     def __iter__(self):
-        return self.__next__()
-    def __next__(self):
         for item in self.map.keys():
             yield (bytes(item, "utf-8"), self.func(item))
+    def __next__(self):
+        try:
+            return self._iter.__next__()
+        except StopIteration:
+            return None
     def __exit__(self, *args, **kwargs):
         pass
     def iter(self):
         for item in self.map.keys():
             yield (bytes(item, "utf-8"), self.func(item))
-        yield None
     def first(self):
         self._iter = self.__iter__()
-        return self.next()
+        try:
+            return next(self._iter)
+        except:
+            return
     def next(self):
         try:
             return next(self._iter)
         except:
-            return None
+            return
     def close(self):
         pass
 
@@ -1099,73 +1105,83 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return obj.handle
 
     def commit_person(self, person, trans, change_time=None):
-        if person.handle in self.person_map:
-            self.emit("person-update", ([person.handle],))
-        else:
-            self.emit("person-add", ([person.handle],))
+        if not trans.batch:
+            if person.handle in self.person_map:
+                self.emit("person-update", ([person.handle],))
+            else:
+                self.emit("person-add", ([person.handle],))
         self.person_map[person.handle] = person
 
     def commit_family(self, family, trans, change_time=None):
-        if family.handle in self.family_map:
-            self.emit("family-update", ([family.handle],))
-        else:
-            self.emit("family-add", ([family.handle],))
+        if not trans.batch:
+            if family.handle in self.family_map:
+                self.emit("family-update", ([family.handle],))
+            else:
+                self.emit("family-add", ([family.handle],))
         self.family_map[family.handle] = family
 
     def commit_citation(self, citation, trans, change_time=None):
-        if citation.handle in self.citation_map:
-            self.emit("citation-update", ([citation.handle],))
-        else:
-            self.emit("citation-add", ([citation.handle],))
+        if not trans.batch:
+            if citation.handle in self.citation_map:
+                self.emit("citation-update", ([citation.handle],))
+            else:
+                self.emit("citation-add", ([citation.handle],))
         self.citation_map[citation.handle] = citation
 
     def commit_source(self, source, trans, change_time=None):
-        if source.handle in self.source_map:
-            self.emit("source-update", ([source.handle],))
-        else:
-            self.emit("source-add", ([source.handle],))
+        if not trans.batch:
+            if source.handle in self.source_map:
+                self.emit("source-update", ([source.handle],))
+            else:
+                self.emit("source-add", ([source.handle],))
         self.source_map[source.handle] = source
 
     def commit_repository(self, repository, trans, change_time=None):
-        if repository.handle in self.repository_map:
-            self.emit("repository-update", ([repository.handle],))
-        else:
-            self.emit("repository-add", ([repository.handle],))
+        if not trans.batch:
+            if repository.handle in self.repository_map:
+                self.emit("repository-update", ([repository.handle],))
+            else:
+                self.emit("repository-add", ([repository.handle],))
         self.repository_map[repository.handle] = repository
 
     def commit_note(self, note, trans, change_time=None):
-        if note.handle in self.note_map:
-            self.emit("note-update", ([note.handle],))
-        else:
-            self.emit("note-add", ([note.handle],))
+        if not trans.batch:
+            if note.handle in self.note_map:
+                self.emit("note-update", ([note.handle],))
+            else:
+                self.emit("note-add", ([note.handle],))
         self.note_map[note.handle] = note
 
     def commit_place(self, place, trans, change_time=None):
-        if place.handle in self.place_map:
-            self.emit("place-update", ([place.handle],))
-        else:
-            self.emit("place-add", ([place.handle],))
+        if not trans.batch:
+            if place.handle in self.place_map:
+                self.emit("place-update", ([place.handle],))
+            else:
+                self.emit("place-add", ([place.handle],))
         self.place_map[place.handle] = place
 
     def commit_event(self, event, trans, change_time=None):
-        if event.handle in self.event_map:
-            self.emit("event-update", ([event.handle],))
-        else:
-            self.emit("event-add", ([event.handle],))
+        if not trans.batch:
+            if event.handle in self.event_map:
+                self.emit("event-update", ([event.handle],))
+            else:
+                self.emit("event-add", ([event.handle],))
         self.event_map[event.handle] = event
 
     def commit_tag(self, tag, trans, change_time=None):
-        if tag.handle in self.tag_map:
-            self.emit("tag-update", ([tag.handle],))
-        else:
-            self.emit("tag-add", ([tag.handle],))
+        if not trans.batch:
+            if tag.handle in self.tag_map:
+                self.emit("tag-update", ([tag.handle],))
+            else:
+                self.emit("tag-add", ([tag.handle],))
         self.tag_map[tag.handle] = tag
 
     def commit_media_object(self, media, transaction, change_time=None):
-        if media.handle in self.media_map:
-            self.emit("media-update", ([media.handle],))
-        else:
-            self.emit("media-add", ([media.handle],))
+        if not trans.batch:
+            if media.handle in self.media_map:
+                self.emit("media-update", ([media.handle],))
+            else:
+                self.emit("media-add", ([media.handle],))
         self.media_map[media.handle] = media
 
     def get_gramps_ids(self, obj_key):
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index e380c9601..852afe779 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -127,13 +127,17 @@ class Cursor(object):
     def __init__(self, model, func):
         self.model = model
         self.func = func
+        self._iter = self.__iter__()
     def __enter__(self):
         return self
     def __iter__(self):
-        return self.__next__()
-    def __next__(self):
         for item in self.model.all():
             yield (bytes(item.handle, "utf-8"), self.func(item.handle))
+    def __next__(self):
+        try:
+            return self._iter.__next__()
+        except StopIteration:
+            return None
     def __exit__(self, *args, **kwargs):
         pass
     def iter(self):
@@ -142,7 +146,10 @@ class Cursor(object):
         yield None
     def first(self):
         self._iter = self.__iter__()
-        return self.next()
+        try:
+            return next(self._iter)
+        except:
+            return
     def next(self):
         try:
             return next(self._iter)
@@ -1494,10 +1501,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 items[0].delete()
             self.dji.add_person(person.serialize())
             self.dji.add_person_detail(person.serialize())
-            if items.count() > 0:
-                self.emit("person-update", ([person.handle],))
-            else:
-                self.emit("person-add", ([person.handle],))
+            if not trans.batch:
+                if items.count() > 0:
+                    self.emit("person-update", ([person.handle],))
+                else:
+                    self.emit("person-add", ([person.handle],))
 
     def commit_family(self, family, trans, change_time=None):
         if self.use_import_cache:
@@ -1509,10 +1517,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 items[0].delete()
             self.dji.add_family(family.serialize())
             self.dji.add_family_detail(family.serialize())
-            if items.count() > 0:
-                self.emit("family-update", ([family.handle],))
-            else:
-                self.emit("family-add", ([family.handle],))
+            if not trans.batch:
+                if items.count() > 0:
+                    self.emit("family-update", ([family.handle],))
+                else:
+                    self.emit("family-add", ([family.handle],))
 
     def commit_citation(self, citation, trans, change_time=None):
         if self.use_import_cache:
@@ -1524,10 +1533,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 items[0].delete()
             self.dji.add_citation(citation.serialize())
             self.dji.add_citation_detail(citation.serialize())
-            if items.count() > 0:
-                self.emit("citation-update", ([citation.handle],))
-            else:
-                self.emit("citation-add", ([citation.handle],))
+            if not trans.batch:
+                if items.count() > 0:
+                    self.emit("citation-update", ([citation.handle],))
+                else:
+                    self.emit("citation-add", ([citation.handle],))
 
     def commit_source(self, source, trans, change_time=None):
         if self.use_import_cache:
@@ -1539,10 +1549,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 items[0].delete()
             self.dji.add_source(source.serialize())
             self.dji.add_source_detail(source.serialize())
-            if items.count() > 0:
-                self.emit("source-update", ([source.handle],))
-            else:
-                self.emit("source-add", ([source.handle],))
+            if not trans.batch:
+                if items.count() > 0:
+                    self.emit("source-update", ([source.handle],))
+                else:
+                    self.emit("source-add", ([source.handle],))
 
     def commit_repository(self, repository, trans, change_time=None):
         if self.use_import_cache:
@@ -1554,10 +1565,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 items[0].delete()
             self.dji.add_repository(repository.serialize())
             self.dji.add_repository_detail(repository.serialize())
-            if items.count() > 0:
-                self.emit("repository-update", ([repository.handle],))
-            else:
-                self.emit("repository-add", ([repository.handle],))
+            if not trans.batch:
+                if items.count() > 0:
+                    self.emit("repository-update", ([repository.handle],))
+                else:
+                    self.emit("repository-add", ([repository.handle],))
 
     def commit_note(self, note, trans, change_time=None):
         if self.use_import_cache:
@@ -1569,10 +1581,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 items[0].delete()
             self.dji.add_note(note.serialize())
             self.dji.add_note_detail(note.serialize())
-            if items.count() > 0:
-                self.emit("note-update", ([note.handle],))
-            else:
-                self.emit("note-add", ([note.handle],))
+            if not trans.batch:
+                if items.count() > 0:
+                    self.emit("note-update", ([note.handle],))
+                else:
+                    self.emit("note-add", ([note.handle],))
 
     def commit_place(self, place, trans, change_time=None):
         if self.use_import_cache:
@@ -1584,10 +1597,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 items[0].delete()
             self.dji.add_place(place.serialize())
             self.dji.add_place_detail(place.serialize())
-            if items.count() > 0:
-                self.emit("place-update", ([place.handle],))
-            else:
-                self.emit("place-add", ([place.handle],))
+            if not trans.batch:
+                if items.count() > 0:
+                    self.emit("place-update", ([place.handle],))
+                else:
+                    self.emit("place-add", ([place.handle],))
 
     def commit_event(self, event, trans, change_time=None):
         if self.use_import_cache:
@@ -1599,10 +1613,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 items[0].delete()
             self.dji.add_event(event.serialize())
             self.dji.add_event_detail(event.serialize())
-            if items.count() > 0:
-                self.emit("event-update", ([event.handle],))
-            else:
-                self.emit("event-add", ([event.handle],))
+            if not trans.batch:
+                if items.count() > 0:
+                    self.emit("event-update", ([event.handle],))
+                else:
+                    self.emit("event-add", ([event.handle],))
 
     def commit_tag(self, tag, trans, change_time=None):
         if self.use_import_cache:
@@ -1614,10 +1629,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 items[0].delete()
             self.dji.add_tag(tag.serialize())
             self.dji.add_tag_detail(tag.serialize())
-            if items.count() > 0:
-                self.emit("tag-update", ([tag.handle],))
-            else:
-                self.emit("tag-add", ([tag.handle],))
+            if not trans.batch:
+                if items.count() > 0:
+                    self.emit("tag-update", ([tag.handle],))
+                else:
+                    self.emit("tag-add", ([tag.handle],))
 
     def commit_media_object(self, media, transaction, change_time=None):
         """
@@ -1633,10 +1649,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 items[0].delete()
             self.dji.add_media(media.serialize())
             self.dji.add_media_detail(media.serialize())
-            if items.count() > 0:
-                self.emit("media-update", ([media.handle],))
-            else:
-                self.emit("media-add", ([media.handle],))
+            if not trans.batch:
+                if items.count() > 0:
+                    self.emit("media-update", ([media.handle],))
+                else:
+                    self.emit("media-add", ([media.handle],))
 
     def get_gramps_ids(self, obj_key):
         key2table = {

From 06d0db3b6a034fe7049f10be76eee2e40f42470e Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Thu, 14 May 2015 11:49:58 -0400
Subject: [PATCH 014/105] DictionaryDb: emit add after actually adding

---
 gramps/plugins/database/dictionarydb.py | 80 ++++++++++++++++++-------
 1 file changed, 60 insertions(+), 20 deletions(-)

diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 1542f4c93..15009d584 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -1105,84 +1105,124 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return obj.handle
 
     def commit_person(self, person, trans, change_time=None):
+        emit = None
         if not trans.batch:
             if person.handle in self.person_map:
-                self.emit("person-update", ([person.handle],))
+                emit = "person-update"
             else:
-                self.emit("person-add", ([person.handle],))
+                emit = "person-add"
         self.person_map[person.handle] = person
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([person.handle],))
 
     def commit_family(self, family, trans, change_time=None):
+        emit = None
         if not trans.batch:
             if family.handle in self.family_map:
-                self.emit("family-update", ([family.handle],))
+                emit = "family-update"
             else:
-                self.emit("family-add", ([family.handle],))
+                emit = "family-add"
         self.family_map[family.handle] = family
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([family.handle],))
 
     def commit_citation(self, citation, trans, change_time=None):
+        emit = None
         if not trans.batch:
             if citation.handle in self.citation_map:
-                self.emit("citation-update", ([citation.handle],))
+                emit = "citation-update"
             else:
-                self.emit("citation-add", ([citation.handle],))
+                emit = "citation-add"
         self.citation_map[citation.handle] = citation
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([citation.handle],))
 
     def commit_source(self, source, trans, change_time=None):
+        emit = None
         if not trans.batch:
             if source.handle in self.source_map:
-                self.emit("source-update", ([source.handle],))
+                emit = "source-update"
             else:
-                self.emit("source-add", ([source.handle],))
+                emit = "source-add"
         self.source_map[source.handle] = source
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([source.handle],))
 
     def commit_repository(self, repository, trans, change_time=None):
+        emit = None
         if not trans.batch:
             if repository.handle in self.repository_map:
-                self.emit("repository-update", ([repository.handle],))
+                emit = "repository-update"
             else:
-                self.emit("repository-add", ([repository.handle],))
+                emit = "repository-add"
         self.repository_map[repository.handle] = repository
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([repository.handle],))
 
     def commit_note(self, note, trans, change_time=None):
+        emit = None
         if not trans.batch:
             if note.handle in self.note_map:
-                self.emit("note-update", ([note.handle],))
+                emit "note-update"
             else:
-                self.emit("note-add", ([note.handle],))
+                emit = "note-add"
         self.note_map[note.handle] = note
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([note.handle],))
 
     def commit_place(self, place, trans, change_time=None):
+        emit = None
         if not trans.batch:
             if place.handle in self.place_map:
-                self.emit("place-update", ([place.handle],))
+                emit = "place-update"
             else:
-                self.emit("place-add", ([place.handle],))
+                emit = "place-add"
         self.place_map[place.handle] = place
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([place.handle],))
 
     def commit_event(self, event, trans, change_time=None):
+        emit = None
         if not trans.batch:
             if event.handle in self.event_map:
-                self.emit("event-update", ([event.handle],))
+                emit = "event-update"
             else:
-                self.emit("event-add", ([event.handle],))
+                emit = "event-add"
         self.event_map[event.handle] = event
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([event.handle],))
 
     def commit_tag(self, tag, trans, change_time=None):
+        emit = None
         if not trans.batch:
             if tag.handle in self.tag_map:
-                self.emit("tag-update", ([tag.handle],))
+                emit = "tag-update"
             else:
-                self.emit("tag-add", ([tag.handle],))
+                emit = "tag-add"
         self.tag_map[tag.handle] = tag
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([tag.handle],))
 
     def commit_media_object(self, media, transaction, change_time=None):
+        emit = None
         if not trans.batch:
             if media.handle in self.media_map:
-                self.emit("media-update", ([media.handle],))
+                emit = "media-update"
             else:
-                self.emit("media-add", ([media.handle],))
+                emit = "media-add"
         self.media_map[media.handle] = media
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([media.handle],))
 
     def get_gramps_ids(self, obj_key):
         key2table = {

From 6e0b8ccf86c5b3b8f8f59c10596e92bad63ab9a4 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Thu, 14 May 2015 11:51:56 -0400
Subject: [PATCH 015/105] DictionaryDb: emit add after actually adding (fixed
 typo)

---
 gramps/plugins/database/dictionarydb.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 15009d584..5c167fe48 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -1168,7 +1168,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         emit = None
         if not trans.batch:
             if note.handle in self.note_map:
-                emit "note-update"
+                emit = "note-update"
             else:
                 emit = "note-add"
         self.note_map[note.handle] = note

From 74330122bd90dffbc502b10d6fd07afb5aa8130d Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Thu, 14 May 2015 12:30:30 -0400
Subject: [PATCH 016/105] Basic infrastructure for Undo/Redo

---
 gramps/gen/db/undoredo.py               | 136 ++++++++++++++++++++++++
 gramps/plugins/database/dictionarydb.py |   5 +-
 gramps/plugins/database/djangodb.py     |   3 +
 3 files changed, 143 insertions(+), 1 deletion(-)
 create mode 100644 gramps/gen/db/undoredo.py

diff --git a/gramps/gen/db/undoredo.py b/gramps/gen/db/undoredo.py
new file mode 100644
index 000000000..b12735093
--- /dev/null
+++ b/gramps/gen/db/undoredo.py
@@ -0,0 +1,136 @@
+import time
+from collections import deque
+
+class DbUndo(object):
+    """
+    Base class for the Gramps undo/redo manager.  Needs to be subclassed
+    for use with a real backend.
+    """
+
+    __slots__ = ('undodb', 'db', 'mapbase', 'undo_history_timestamp',
+                 'txn', 'undoq', 'redoq')
+
+    def __init__(self, grampsdb):
+        """
+        Class constructor. Set up main instance variables
+        """
+        self.db = grampsdb
+        self.undoq = deque()
+        self.redoq = deque()
+        self.undo_history_timestamp = time.time()
+        self.txn = None
+
+    def clear(self):
+        """
+        Clear the undo/redo list (but not the backing storage)
+        """
+        self.undoq.clear()
+        self.redoq.clear()
+        self.undo_history_timestamp = time.time()
+        self.txn = None
+
+    def __enter__(self, value):
+        """
+        Context manager method to establish the context
+        """
+        self.open(value)
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        """
+        Context manager method to finish the context
+        """
+        if exc_type is None:
+            self.close()
+        return exc_type is None
+
+    def open(self, value):
+        """
+        Open the backing storage.  Needs to be overridden in the derived
+        class.
+        """
+        raise NotImplementedError
+
+    def close(self):
+        """
+        Close the backing storage.  Needs to be overridden in the derived
+        class.
+        """        
+        raise NotImplementedError
+
+    def append(self, value):
+        """
+        Add a new entry on the end.  Needs to be overridden in the derived
+        class.
+        """        
+        raise NotImplementedError
+
+    def __getitem__(self, index):
+        """
+        Returns an entry by index number.  Needs to be overridden in the
+        derived class.
+        """        
+        raise NotImplementedError
+
+    def __setitem__(self, index, value):
+        """
+        Set an entry to a value.  Needs to be overridden in the derived class.
+        """           
+        raise NotImplementedError
+
+    def __len__(self):
+        """
+        Returns the number of entries.  Needs to be overridden in the derived
+        class.
+        """         
+        raise NotImplementedError
+
+    def commit(self, txn, msg):
+        """
+        Commit the transaction to the undo/redo database.  "txn" should be
+        an instance of Gramps transaction class
+        """
+        txn.set_description(msg)
+        txn.timestamp = time.time()
+        self.undoq.append(txn)
+
+    def undo(self, update_history=True):
+        """
+        Undo a previously committed transaction
+        """
+        if self.db.readonly or self.undo_count == 0:
+            return False
+        return self.__undo(update_history)
+
+    def redo(self, update_history=True):
+        """
+        Redo a previously committed, then undone, transaction
+        """
+        if self.db.readonly or self.redo_count == 0:
+            return False
+        return self.__redo(update_history)
+
+    def undoredo(func):
+        """
+        Decorator function to wrap undo and redo operations within a bsddb
+        transaction.  It also catches bsddb errors and raises an exception
+        as appropriate
+        """
+        pass
+
+    def __redo(self, update_history=True):
+        """
+        Access the last undone transaction, and revert the data to the state 
+        before the transaction was undone.
+        """
+        pass
+
+    def __undo(self, db=None, update_history=True):
+        """
+        Access the last committed transaction, and revert the data to the 
+        state before the transaction was committed.
+        """
+        pass
+
+    undo_count = property(lambda self:len(self.undoq))
+    redo_count = property(lambda self:len(self.redoq))
diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 5c167fe48..09748295a 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -37,6 +37,7 @@ import logging
 #
 #------------------------------------------------------------------------
 from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn, KEY_TO_NAME_MAP
+from gramps.gen.db.undoredo import DbUndo
 from gramps.gen.db.dbconst import *
 from gramps.gen.utils.callback import Callback
 from gramps.gen.updatecallback import UpdateCallback
@@ -390,6 +391,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.modified   = 0
         self.txn = DictionaryTxn("DbDictionary Transaction", self)
         self.transaction = None
+        self.undodb = DbUndo(self)
+        self.abort_possible = False
         self._directory = directory
         if directory:
             self.load(directory)
@@ -1212,7 +1215,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if emit:
             self.emit(emit, ([tag.handle],))
 
-    def commit_media_object(self, media, transaction, change_time=None):
+    def commit_media_object(self, media, trans, change_time=None):
         emit = None
         if not trans.batch:
             if media.handle in self.media_map:
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index 852afe779..0a88733cd 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -44,6 +44,7 @@ from gramps.gen.lib import (Person, Family, Event, Place, Repository,
                             Citation, Source, Note, MediaObject, Tag, 
                             Researcher)
 from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn
+from gramps.gen.db.undoredo import DbUndo
 from gramps.gen.utils.callback import Callback
 from gramps.gen.updatecallback import UpdateCallback
 from gramps.gen.db import (PERSON_KEY,
@@ -335,6 +336,8 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.import_cache = {}
         self.use_import_cache = False
         self.use_db_cache = True
+        self.undodb = DbUndo(self)
+        self.abort_possible = False
         self._directory = directory
         if directory:
             self.load(directory)

From ad83d8477889e837b289cd8d685ecf2029c8bb62 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Thu, 14 May 2015 12:43:24 -0400
Subject: [PATCH 017/105] DjangoDb: send proper object-add signal on new
 objects

---
 gramps/plugins/database/djangodb.py | 50 +++++++++++++++++------------
 1 file changed, 30 insertions(+), 20 deletions(-)

diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index 0a88733cd..305cde2a8 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -1499,13 +1499,14 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         else:
             raw = person.serialize()
             items = self.dji.Person.filter(handle=person.handle)
-            if items.count() > 0:
+            count = items.count()
+            if count > 0:
                 # Hack, for the moment: delete and re-add
                 items[0].delete()
             self.dji.add_person(person.serialize())
             self.dji.add_person_detail(person.serialize())
             if not trans.batch:
-                if items.count() > 0:
+                if count > 0:
                     self.emit("person-update", ([person.handle],))
                 else:
                     self.emit("person-add", ([person.handle],))
@@ -1516,12 +1517,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         else:
             raw = family.serialize()
             items = self.dji.Family.filter(handle=family.handle)
-            if items.count() > 0:
+            count = items.count()
+            if count > 0:
                 items[0].delete()
             self.dji.add_family(family.serialize())
             self.dji.add_family_detail(family.serialize())
             if not trans.batch:
-                if items.count() > 0:
+                if count > 0:
                     self.emit("family-update", ([family.handle],))
                 else:
                     self.emit("family-add", ([family.handle],))
@@ -1532,12 +1534,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         else:
             raw = citation.serialize()
             items = self.dji.Citation.filter(handle=citation.handle)
-            if items.count() > 0:
+            count = items.count()
+            if count > 0:
                 items[0].delete()
             self.dji.add_citation(citation.serialize())
             self.dji.add_citation_detail(citation.serialize())
             if not trans.batch:
-                if items.count() > 0:
+                if count > 0:
                     self.emit("citation-update", ([citation.handle],))
                 else:
                     self.emit("citation-add", ([citation.handle],))
@@ -1548,12 +1551,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         else:
             raw = source.serialize()
             items = self.dji.Source.filter(handle=source.handle)
-            if items.count() > 0:
+            count = items.count()
+            if count > 0:
                 items[0].delete()
             self.dji.add_source(source.serialize())
             self.dji.add_source_detail(source.serialize())
             if not trans.batch:
-                if items.count() > 0:
+                if count > 0:
                     self.emit("source-update", ([source.handle],))
                 else:
                     self.emit("source-add", ([source.handle],))
@@ -1564,12 +1568,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         else:
             raw = repository.serialize()
             items = self.dji.Repository.filter(handle=repository.handle)
-            if items.count() > 0:
+            count = items.count()
+            if count > 0:
                 items[0].delete()
             self.dji.add_repository(repository.serialize())
             self.dji.add_repository_detail(repository.serialize())
             if not trans.batch:
-                if items.count() > 0:
+                if count > 0:
                     self.emit("repository-update", ([repository.handle],))
                 else:
                     self.emit("repository-add", ([repository.handle],))
@@ -1580,12 +1585,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         else:
             raw = note.serialize()
             items = self.dji.Note.filter(handle=note.handle)
-            if items.count() > 0:
+            count = items.count()
+            if count > 0:
                 items[0].delete()
             self.dji.add_note(note.serialize())
             self.dji.add_note_detail(note.serialize())
             if not trans.batch:
-                if items.count() > 0:
+                if count > 0:
                     self.emit("note-update", ([note.handle],))
                 else:
                     self.emit("note-add", ([note.handle],))
@@ -1596,12 +1602,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         else:
             raw = place.serialize()
             items = self.dji.Place.filter(handle=place.handle)
-            if items.count() > 0:
+            count = items.count()
+            if count > 0:
                 items[0].delete()
             self.dji.add_place(place.serialize())
             self.dji.add_place_detail(place.serialize())
             if not trans.batch:
-                if items.count() > 0:
+                if count > 0:
                     self.emit("place-update", ([place.handle],))
                 else:
                     self.emit("place-add", ([place.handle],))
@@ -1612,12 +1619,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         else:
             raw = event.serialize()
             items = self.dji.Event.filter(handle=event.handle)
-            if items.count() > 0:
+            count = items.count()
+            if count > 0:
                 items[0].delete()
             self.dji.add_event(event.serialize())
             self.dji.add_event_detail(event.serialize())
             if not trans.batch:
-                if items.count() > 0:
+                if count > 0:
                     self.emit("event-update", ([event.handle],))
                 else:
                     self.emit("event-add", ([event.handle],))
@@ -1628,12 +1636,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         else:
             raw = tag.serialize()
             items = self.dji.Tag.filter(handle=tag.handle)
-            if items.count() > 0:
+            count = items.count()
+            if count > 0:
                 items[0].delete()
             self.dji.add_tag(tag.serialize())
             self.dji.add_tag_detail(tag.serialize())
             if not trans.batch:
-                if items.count() > 0:
+                if count > 0:
                     self.emit("tag-update", ([tag.handle],))
                 else:
                     self.emit("tag-add", ([tag.handle],))
@@ -1648,12 +1657,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         else:
             raw = media.serialize()
             items = self.dji.Media.filter(handle=media.handle)
-            if items.count() > 0:
+            count = items.count()
+            if count > 0:
                 items[0].delete()
             self.dji.add_media(media.serialize())
             self.dji.add_media_detail(media.serialize())
             if not trans.batch:
-                if items.count() > 0:
+                if count > 0:
                     self.emit("media-update", ([media.handle],))
                 else:
                     self.emit("media-add", ([media.handle],))

From 37e7ead1e2a07a45577d2bd069132159c886602b Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Thu, 14 May 2015 12:48:55 -0400
Subject: [PATCH 018/105] Fixed About dialog to show proper BSDDB version

---
 gramps/gui/aboutdialog.py | 28 ++++++++++++++--------------
 1 file changed, 14 insertions(+), 14 deletions(-)

diff --git a/gramps/gui/aboutdialog.py b/gramps/gui/aboutdialog.py
index 7ab181cc2..a80e3f26b 100644
--- a/gramps/gui/aboutdialog.py
+++ b/gramps/gui/aboutdialog.py
@@ -29,12 +29,6 @@ import os
 import sys
 import io
 
-try:
-    import bsddb3 as bsddb ## ok, in try/except
-    BSDDB_STR = ellipses(str(bsddb.__version__) + " " + str(bsddb.db.version()))
-except:
-    BSDDB_STR = 'not found'
-
 ##import logging
 ##_LOG = logging.getLogger(".GrampsAboutDialog")
 
@@ -65,6 +59,20 @@ _ = glocale.translation.gettext
 from gramps.gen.constfunc import get_env_var
 from .display import display_url
 
+def ellipses(text):
+    """
+    Ellipsize text on length 40
+    """
+    if len(text) > 40:
+        return text[:40] + "..."
+    return text
+
+try:
+    import bsddb3 as bsddb ## ok, in try/except
+    BSDDB_STR = ellipses(str(bsddb.__version__) + " " + str(bsddb.db.version()))
+except:
+    BSDDB_STR = 'not found'
+
 #-------------------------------------------------------------------------
 #
 # GrampsAboutDialog
@@ -135,14 +143,6 @@ class GrampsAboutDialog(Gtk.AboutDialog):
                    ellipses(operatingsystem),
                    ellipses(distribution)))
 
-def ellipses(text):
-    """
-    Ellipsize text on length 40
-    """
-    if len(text) > 40:
-        return text[:40] + "..."
-    return text
-
 #-------------------------------------------------------------------------
 #
 # AuthorParser

From c1345ca64c155b0d31ddec21c45e187bcdebd0e4 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Thu, 14 May 2015 22:35:50 -0400
Subject: [PATCH 019/105] Removed hardcoded database backend types

---
 gramps/gui/dbman.py | 43 +++++++++++++++++++++++++++----------------
 1 file changed, 27 insertions(+), 16 deletions(-)

diff --git a/gramps/gui/dbman.py b/gramps/gui/dbman.py
index 8054b41b8..2473569f6 100644
--- a/gramps/gui/dbman.py
+++ b/gramps/gui/dbman.py
@@ -69,6 +69,7 @@ from gi.repository import Pango
 #
 #-------------------------------------------------------------------------
 from gramps.gen.const import GRAMPS_LOCALE as glocale
+from gramps.gen.plug import BasePluginManager
 _ = glocale.translation.gettext
 from gramps.gen.const import URL_WIKISTRING
 from .user import User
@@ -104,7 +105,10 @@ STOCK_COL = 6
 RCS_BUTTON = { True : _('_Extract'), False : _('_Archive') }
 
 class DatabaseDialog(Gtk.MessageDialog):
-    def __init__(self, parent=None):
+    def __init__(self, parent, options):
+        """
+        options = [(pdata, number), ...]
+        """
         Gtk.MessageDialog.__init__(self,
                                 parent,
                                 flags=Gtk.DialogFlags.MODAL,
@@ -112,15 +116,12 @@ class DatabaseDialog(Gtk.MessageDialog):
                                    )
         self.set_icon(ICON)
         self.set_title('')
-
         self.set_markup('<span size="larger" weight="bold">%s</span>' %
-                        _('Database Backend'))
+                        _('Database Backend for New Tree'))
         self.format_secondary_text(
-            _("Please select a database backend type"))
-
-        self.add_button("BSDDB Database (standard)", 1)
-        self.add_button("Dictionary (in-memory)", 2)
-        self.add_button("Django Database", 3)
+            _("Please select a database backend type:"))
+        for option, number in options:
+            self.add_button(option.name, number)
 
 class DbManager(CLIDbManager):
     """
@@ -779,14 +780,24 @@ class DbManager(CLIDbManager):
         message.
         """
         self.new.set_sensitive(False)
-        # popup window and ask for dbid types, if more than one
-        ## FIXME: autoload from plugins
-        dbid = "bsddb"
-        d = DatabaseDialog(self.top)
-        database = d.run()
-        d.destroy()
-        if database >= 0:
-            dbid = {1:"bsddb",2:"dictionarydb",3:"djangodb"}[database]
+        dbid = None
+        pmgr = BasePluginManager.get_instance()
+        pdata = pmgr.get_reg_databases()
+        # If just one database backend, just use it:
+        if len(pdata) == 0:
+            DbManager.ERROR(_("No available database backends"),
+                            _("Please check your dependencies."))
+        elif len(pdata) == 1:
+            dbid = pdata[0].id
+        elif len(pdata) > 1:
+            options = sorted(list(zip(pdata, range(1, len(pdata) + 1))), key=lambda items: items[0].name)
+            d = DatabaseDialog(self.top, options)
+            number = d.run()
+            d.destroy()
+            if number >= 0:
+                dbid = [option[0].id for option in options if option[1] == number][0]
+        ### Now, let's load it up
+        if dbid:
             try:
                 self._create_new_db(dbid=dbid)
             except (OSError, IOError) as msg:

From b6fb46b760b905ddbd5c800e965309dc931b78e9 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Thu, 14 May 2015 22:52:57 -0400
Subject: [PATCH 020/105] Database plugin type support reset_system, to reset
 modules

---
 gramps/gen/dbstate.py                   | 20 +++++++++-----------
 gramps/gen/plug/_pluginreg.py           | 13 +++++++++++++
 gramps/plugins/database/djangodb.gpr.py |  1 +
 3 files changed, 23 insertions(+), 11 deletions(-)

diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py
index 069934a13..0d9c30d4a 100644
--- a/gramps/gen/dbstate.py
+++ b/gramps/gen/dbstate.py
@@ -140,18 +140,16 @@ class DbState(Callback):
             pmgr.reg_plugins(USER_PLUGINS, self, None, load_on_reg=True)
             pdata = pmgr.get_plugin(id)
 
-        ### FIXME: Currently Django needs to reset modules
-        if pdata.id == "djangodb":
-            if self.modules_is_set():
-                self.reset_modules()
-            else:
-                self.save_modules()
+        if pdata:
+            if pdata.reset_system:
+                if self.modules_is_set():
+                    self.reset_modules()
+                else:
+                    self.save_modules()
+            mod = pmgr.load_plugin(pdata)
+            database = getattr(mod, pdata.databaseclass)
+            return database()
 
-        mod = pmgr.load_plugin(pdata)
-        database = getattr(mod, pdata.databaseclass)
-        return database()
-
-    ## FIXME:
     ## Work-around for databases that need sys refresh (django):
     def modules_is_set(self):
         if hasattr(self, "_modules"):
diff --git a/gramps/gen/plug/_pluginreg.py b/gramps/gen/plug/_pluginreg.py
index 5619f18ed..70a93f0f2 100644
--- a/gramps/gen/plug/_pluginreg.py
+++ b/gramps/gen/plug/_pluginreg.py
@@ -356,6 +356,9 @@ class PluginData(object):
 
     .. attribute:: databaseclass
        The class in the module that is the database class
+    .. attribute:: reset_system
+       Boolean to indicate that the system (sys.modules) should
+       be reset.
     """
 
     def __init__(self):
@@ -430,6 +433,7 @@ class PluginData(object):
         self._order = END
         #DATABASE attr
         self._databaseclass = None
+        self._reset_system = False
         #GENERAL attr
         self._data = []
         self._process = None
@@ -949,7 +953,16 @@ class PluginData(object):
     def _get_databaseclass(self):
         return self._databaseclass
 
+    def _set_reset_system(self, reset_system):
+        if not self._ptype == DATABASE:
+            raise ValueError('reset_system may only be set for DATABASE plugins')
+        self._reset_system = reset_system
+
+    def _get_reset_system(self):
+        return self._reset_system
+
     databaseclass = property(_get_databaseclass, _set_databaseclass)
+    reset_system = property(_get_reset_system, _set_reset_system)
 
     #GENERAL attr
     def _set_data(self, data):
diff --git a/gramps/plugins/database/djangodb.gpr.py b/gramps/plugins/database/djangodb.gpr.py
index 57b30f2f5..2d0d0797b 100644
--- a/gramps/plugins/database/djangodb.gpr.py
+++ b/gramps/plugins/database/djangodb.gpr.py
@@ -29,3 +29,4 @@ plg.status = STABLE
 plg.fname = 'djangodb.py'
 plg.ptype = DATABASE
 plg.databaseclass = 'DbDjango'
+plg.reset_system = True

From c2fb186edcf5e21d6ef1a589bd8cd22452776dd1 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Thu, 14 May 2015 23:15:30 -0400
Subject: [PATCH 021/105] Added missing bookmark count methods to djangodb and
 dictionarydb

---
 gramps/plugins/database/dictionarydb.py | 13 +++++++++++++
 gramps/plugins/database/djangodb.py     | 14 ++++++++++++++
 2 files changed, 27 insertions(+)

diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 09748295a..2a15c28b1 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -393,6 +393,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.transaction = None
         self.undodb = DbUndo(self)
         self.abort_possible = False
+        self._bm_changes = 0
         self._directory = directory
         if directory:
             self.load(directory)
@@ -1689,3 +1690,15 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         with open(versionpath, "w") as version_file:
             version_file.write("dictionarydb")
 
+    def report_bm_change(self):
+        """
+        Add 1 to the number of bookmark changes during this session.
+        """
+        self._bm_changes += 1
+
+    def db_has_bm_changes(self):
+        """
+        Return whethere there were bookmark changes during the session.
+        """
+        return self._bm_changes > 0
+
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index 305cde2a8..543243c99 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -338,6 +338,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.use_db_cache = True
         self.undodb = DbUndo(self)
         self.abort_possible = False
+        self._bm_changes = 0
         self._directory = directory
         if directory:
             self.load(directory)
@@ -2088,3 +2089,16 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         for filename in os.listdir(defaults):
             fullpath = os.path.abspath(os.path.join(defaults, filename))
             shutil.copy2(fullpath, directory)
+
+    def report_bm_change(self):
+        """
+        Add 1 to the number of bookmark changes during this session.
+        """
+        self._bm_changes += 1
+
+    def db_has_bm_changes(self):
+        """
+        Return whethere there were bookmark changes during the session.
+        """
+        return self._bm_changes > 0
+

From ae11d8b48440270fc8eaf93ed1c3394c0dfaf1f1 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Fri, 15 May 2015 06:42:13 -0400
Subject: [PATCH 022/105] Alternative DBs: touch meta_data.db to record last
 access time

---
 gramps/plugins/database/dictionarydb.py |  9 +++++++++
 gramps/plugins/database/djangodb.py     | 11 ++++++++++-
 2 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 2a15c28b1..ff1b8f745 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -67,6 +67,13 @@ from gramps.gen.lib.tag import Tag
 
 _LOG = logging.getLogger(DBLOGNAME)
 
+def touch(fname, mode=0o666, dir_fd=None, **kwargs):
+    ## After http://stackoverflow.com/questions/1158076/implement-touch-using-python
+    flags = os.O_CREAT | os.O_APPEND
+    with os.fdopen(os.open(fname, flags=flags, mode=mode, dir_fd=dir_fd)) as f:
+        os.utime(f.fileno() if os.utime in os.supports_fd else fname,
+                 dir_fd=None if os.supports_fd else dir_fd, **kwargs)
+
 class Environment(object):
     """
     Implements the Environment API.
@@ -1483,6 +1490,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             writer = XmlWriter(self, User(), strip_photos=0, compress=1)
             filename = os.path.join(self._directory, "data.gramps")
             writer.write(filename)
+            filename = os.path.join(self._directory, "meta_data.db")
+            touch(filename)
 
     def find_backlink_handles(self, handle, include_classes=None):
         return []
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index 543243c99..759d366b8 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -64,6 +64,13 @@ sys.path.append(os.path.dirname(os.path.abspath(__file__)))
 
 _LOG = logging.getLogger(DBLOGNAME)
 
+def touch(fname, mode=0o666, dir_fd=None, **kwargs):
+    ## After http://stackoverflow.com/questions/1158076/implement-touch-using-python
+    flags = os.O_CREAT | os.O_APPEND
+    with os.fdopen(os.open(fname, flags=flags, mode=mode, dir_fd=dir_fd)) as f:
+        os.utime(f.fileno() if os.utime in os.supports_fd else fname,
+                 dir_fd=None if os.supports_fd else dir_fd, **kwargs)
+
 class Environment(object):
     """
     Implements the Environment API.
@@ -1959,7 +1966,9 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return None
 
     def close(self):
-        pass
+        if self._directory:
+            filename = os.path.join(self._directory, "meta_data.db")
+            touch(filename)
 
     def get_surname_list(self):
         return []

From a36a8b72b80a231f14a7660a486f61f419fcbaa1 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Fri, 15 May 2015 07:32:03 -0400
Subject: [PATCH 023/105] Database API, -L: database reports summary, if
 possible

---
 gramps/cli/clidbman.py                        | 54 ++++++++++++-------
 gramps/plugins/database/bsddb_support/read.py | 13 +++++
 gramps/plugins/database/dictionarydb.py       | 29 ++++++++++
 gramps/plugins/database/djangodb.py           | 32 +++++++++--
 4 files changed, 107 insertions(+), 21 deletions(-)

diff --git a/gramps/cli/clidbman.py b/gramps/cli/clidbman.py
index eb7a59ffa..b8ee1d294 100644
--- a/gramps/cli/clidbman.py
+++ b/gramps/cli/clidbman.py
@@ -133,12 +133,42 @@ class CLIDbManager(object):
 
     def get_dbdir_summary(self, dirpath, name):
         """
-        Returns (people_count, bsddb_version, schema_version) of
-        current DB.
-        Returns ("Unknown", "Unknown", "Unknown") if invalid DB or other error.
+        dirpath: full path to database
+        name: proper name of family tree
+
+        Returns dictionary of summary item.
+        Should include at least, if possible:
+
+        _("Path")
+        _("Family Tree")
+        _("Last accessed")
+        _("Database backend")
+        _("Locked?")
+
+        and these details:
+
+        _("Number of people")
+        _("Version")
+        _("Schema version")
         """
-        ## Maybe return the txt file contents, for now
-        return ("Unknown", "Unknown", "Unknown")
+        dbid = "bsddb"
+        dbid_path = os.path.join(dirpath, "database.txt")
+        if os.path.isfile(dbid_path):
+            dbid = open(dbid_path).read().strip()
+        try:
+            database = self.dbstate.make_database(dbid)
+            database.load(dirpath, None)
+            retval = database.get_summary()
+        except Exception as msg:
+            retval = {"Unavailable": str(msg)[:74] + "..."}
+        retval.update({
+            _("Family Tree"): name,
+            _("Path"): dirpath,
+            _("Database backend"): dbid,
+            _("Last accessed"): time_val(dirpath)[1],
+            _("Locked?"): self.is_locked(dirpath),
+        })
+        return retval
 
     def family_tree_summary(self):
         """
@@ -149,19 +179,7 @@ class CLIDbManager(object):
         for item in self.current_names:
             (name, dirpath, path_name, last, 
              tval, enable, stock_id) = item
-            count, bsddb_version, schema_version = self.get_dbdir_summary(dirpath, name)
-            retval = {}
-            retval[_("Number of people")] = count
-            if enable:
-                retval[_("Locked?")] = _("yes")
-            else:
-                retval[_("Locked?")] = _("no")
-            retval[_("Bsddb version")] = bsddb_version
-            retval[_("Schema version")] = schema_version
-            retval[_("Family Tree")] = name
-            retval[_("Path")] = dirpath
-            retval[_("Last accessed")] = time.strftime('%x %X', 
-                                                    time.localtime(tval))
+            retval = self.get_dbdir_summary(dirpath, name)
             summary_list.append( retval )
         return summary_list
 
diff --git a/gramps/plugins/database/bsddb_support/read.py b/gramps/plugins/database/bsddb_support/read.py
index d51810581..9280dbc09 100644
--- a/gramps/plugins/database/bsddb_support/read.py
+++ b/gramps/plugins/database/bsddb_support/read.py
@@ -1976,3 +1976,16 @@ class DbBsddbRead(DbReadBase, Callback):
             self.__log_error()
             name = None
         return name
+
+    def get_summary(self):
+        """
+        Returns dictionary of summary item.
+        Should include, if possible:
+
+        _("Number of people")
+        _("Version")
+        _("Schema version")
+        """
+        return {
+            _("Number of people"): self.get_number_of_people(),
+        }
diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index ff1b8f745..72a383469 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -36,6 +36,8 @@ import logging
 # Gramps Modules
 #
 #------------------------------------------------------------------------
+from gramps.gen.const import GRAMPS_LOCALE as glocale
+_ = glocale.translation.gettext
 from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn, KEY_TO_NAME_MAP
 from gramps.gen.db.undoredo import DbUndo
 from gramps.gen.db.dbconst import *
@@ -1711,3 +1713,30 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         """
         return self._bm_changes > 0
 
+    def get_summary(self):
+        """
+        Returns dictionary of summary item.
+        Should include, if possible:
+
+        _("Number of people")
+        _("Version")
+        _("Schema version")
+        """
+        return {
+            _("Number of people"): self.get_number_of_people(),
+        }
+
+    def get_dbname(self):
+        """
+        In DictionaryDb, the database is in a text file at the path
+        """
+        filepath = os.path.join(self._directory, "name.txt")
+        try:
+            name_file = open(filepath, "r")
+            name = name_file.readline().strip()
+            name_file.close()
+        except (OSError, IOError) as msg:
+            _LOG.error(str(msg))
+            name = None
+        return name
+
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index 759d366b8..1c6e3a6a1 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -40,6 +40,8 @@ from django.db import transaction
 #
 #------------------------------------------------------------------------
 import gramps
+from gramps.gen.const import GRAMPS_LOCALE as glocale
+_ = glocale.translation.gettext
 from gramps.gen.lib import (Person, Family, Event, Place, Repository, 
                             Citation, Source, Note, MediaObject, Tag, 
                             Researcher)
@@ -2028,9 +2030,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         ## FIXME
         pass
     
-    def get_dbname(self):
-        return "Django Database"
-
     ## missing
 
     def find_place_child_handles(self, handle):
@@ -2111,3 +2110,30 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         """
         return self._bm_changes > 0
 
+    def get_summary(self):
+        """
+        Returns dictionary of summary item.
+        Should include, if possible:
+
+        _("Number of people")
+        _("Version")
+        _("Schema version")
+        """
+        return {
+            _("Number of people"): self.get_number_of_people(),
+        }
+
+    def get_dbname(self):
+        """
+        In Django, the database is in a text file at the path
+        """
+        filepath = os.path.join(self._directory, "name.txt")
+        try:
+            name_file = open(filepath, "r")
+            name = name_file.readline().strip()
+            name_file.close()
+        except (OSError, IOError) as msg:
+            _LOG.error(str(msg))
+            name = None
+        return name
+

From 16c2843073e0b2ea55de4cd4b5b533d1cca1f986 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Fri, 15 May 2015 09:12:21 -0400
Subject: [PATCH 024/105] bsddb backend: supply version details in get_summary

---
 .../plugins/database/bsddb_support/write.py   | 22 +++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/gramps/plugins/database/bsddb_support/write.py b/gramps/plugins/database/bsddb_support/write.py
index 836fcc364..cca39eae7 100644
--- a/gramps/plugins/database/bsddb_support/write.py
+++ b/gramps/plugins/database/bsddb_support/write.py
@@ -2448,6 +2448,28 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
         except (OSError, IOError) as msg:
             raise DbException(str(msg))
 
+    def get_summary(self):
+        """
+        Returns dictionary of summary item.
+        Should include, if possible:
+
+        _("Number of people")
+        _("Version")
+        _("Schema version")
+        """
+        schema_version = self.metadata.get(b'version', default=None)
+        bdbversion_file = os.path.join(self.path, BDBVERSFN)
+        if os.path.isfile(bdbversion_file):
+            vers_file = open(bdbversion_file)
+            bsddb_version = vers_file.readline().strip()
+        else:
+            bsddb_version = _("Unknown")
+        return {
+            _("Number of people"): self.get_number_of_people(),
+            _("Schema version"): schema_version,
+            _("Version"): bsddb_version,
+        }
+
 def mk_backup_name(database, base):
     """
     Return the backup name of the database table

From 1c05879f62fe95ae6dfb9ed3411fca989a9164a3 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Fri, 15 May 2015 10:48:14 -0400
Subject: [PATCH 025/105] Database API: expore name, full_name, and brief_name

---
 gramps/plugins/database/dictionarydb.py | 9 +++++++++
 gramps/plugins/database/djangodb.py     | 9 +++++++++
 2 files changed, 18 insertions(+)

diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 72a383469..ecdf5a112 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -404,6 +404,9 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.abort_possible = False
         self._bm_changes = 0
         self._directory = directory
+        self.full_name = None
+        self.path = None
+        self.brief_name = None
         if directory:
             self.load(directory)
 
@@ -1671,6 +1674,9 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         from gramps.plugins.importer.importxml import importData
         from gramps.cli.user import User 
         self._directory = directory
+        self.full_name = os.path.abspath(self._directory)
+        self.path = self.full_name
+        self.brief_name = os.path.basename(self._directory)
         filename = os.path.join(directory, "data.gramps")
         if os.path.isfile(filename):
             importData(self, filename, User())
@@ -1690,6 +1696,9 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def set_save_path(self, directory):
         self._directory = directory
+        self.full_name = os.path.abspath(self._directory)
+        self.path = self.full_name
+        self.brief_name = os.path.basename(self._directory)
 
     def undo(self, update_history=True):
         pass
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index 1c6e3a6a1..182295645 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -349,6 +349,9 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.abort_possible = False
         self._bm_changes = 0
         self._directory = directory
+        self.full_name = None
+        self.path = None
+        self.brief_name = None
         if directory:
             self.load(directory)
 
@@ -358,6 +361,9 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
              force_bsddb_downgrade=False,
              force_python_upgrade=False):
         self._directory = directory
+        self.full_name = os.path.abspath(self._directory)
+        self.path = self.full_name
+        self.brief_name = os.path.basename(self._directory)
         from django.conf import settings
         default_settings = {"__file__": 
                             os.path.join(directory, "default_settings.py")}
@@ -1836,6 +1842,9 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def set_save_path(self, directory):
         self._directory = directory
+        self.full_name = os.path.abspath(self._directory)
+        self.path = self.full_name
+        self.brief_name = os.path.basename(self._directory)
 
     ## Get types:
     def get_event_attribute_types(self):

From 87d2cfb3016a25ab5112d397f5803afe9b9879ad Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Fri, 15 May 2015 22:50:54 -0400
Subject: [PATCH 026/105] DictionaryDb: reworked internal reprs; updated gender
 stats, researcher

---
 gramps/plugins/database/dictionarydb.py | 341 ++++++++++++++----------
 gramps/plugins/database/djangodb.py     |  17 +-
 2 files changed, 213 insertions(+), 145 deletions(-)

diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index ecdf5a112..38ad398ac 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -38,7 +38,8 @@ import logging
 #------------------------------------------------------------------------
 from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
-from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn, KEY_TO_NAME_MAP
+from gramps.gen.db import (DbReadBase, DbWriteBase, DbTxn, 
+                           KEY_TO_NAME_MAP, KEY_TO_CLASS_MAP)
 from gramps.gen.db.undoredo import DbUndo
 from gramps.gen.db.dbconst import *
 from gramps.gen.utils.callback import Callback
@@ -66,6 +67,7 @@ from gramps.gen.lib.place import Place
 from gramps.gen.lib.repo import Repository
 from gramps.gen.lib.note import Note
 from gramps.gen.lib.tag import Tag
+from gramps.gen.lib.genderstats import GenderStats
 
 _LOG = logging.getLogger(DBLOGNAME)
 
@@ -99,8 +101,8 @@ class Table(object):
         """
         return self.funcs["cursor_func"]()
 
-    def put(key, data, txn=None):
-        self[key] = data
+    def put(self, key, data, txn=None):
+        self.funcs["add_func"](data, txn)
 
 class Map(dict):
     """
@@ -137,15 +139,14 @@ class MetaCursor(object):
         pass
 
 class Cursor(object):
-    def __init__(self, map, func):
+    def __init__(self, map):
         self.map = map
-        self.func = func
         self._iter = self.__iter__()
     def __enter__(self):
         return self
     def __iter__(self):
         for item in self.map.keys():
-            yield (bytes(item, "utf-8"), self.func(item))
+            yield (bytes(item, "utf-8"), self.map[item])
     def __next__(self):
         try:
             return self._iter.__next__()
@@ -155,7 +156,7 @@ class Cursor(object):
         pass
     def iter(self):
         for item in self.map.keys():
-            yield (bytes(item, "utf-8"), self.func(item))
+            yield (bytes(item, "utf-8"), self.map[item])
     def first(self):
         self._iter = self.__iter__()
         try:
@@ -383,15 +384,25 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.nmap_index = 0
         self.env = Environment(self)
         self.person_map = Map(Table(self._tables["Person"]))
+        self.person_id_map = {}
         self.family_map = Map(Table(self._tables["Family"]))
+        self.family_id_map = {}
         self.place_map  = Map(Table(self._tables["Place"]))
+        self.place_id_map = {}
         self.citation_map = Map(Table(self._tables["Citation"]))
+        self.citation_id_map = {}
         self.source_map = Map(Table(self._tables["Source"]))
+        self.source_id_map = {}
         self.repository_map  = Map(Table(self._tables["Repository"]))
+        self.repository_id_map = {}
         self.note_map = Map(Table(self._tables["Note"]))
+        self.note_id_map = {}
         self.media_map  = Map(Table(self._tables["Media"]))
+        self.media_id_map = {}
         self.event_map  = Map(Table(self._tables["Event"]))
+        self.event_id_map = {}
         self.tag_map  = Map(Table(self._tables["Tag"]))
+        self.tag_id_map = {}
         self.metadata   = Map(Table({"cursor_func": lambda: MetaCursor()}))
         self.name_group = {}
         self.undo_callback = None
@@ -407,6 +418,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.full_name = None
         self.path = None
         self.brief_name = None
+        self.genderStats = GenderStats() # can pass in loaded stats as dict
+        self.owner = Researcher()
         if directory:
             self.load(directory)
 
@@ -425,12 +438,15 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return None
 
     def transaction_commit(self, txn):
+        ## FIXME
         pass
 
     def get_undodb(self):
+        ## FIXME
         return None
 
     def transaction_abort(self, txn):
+        ## FIXME
         pass
 
     @staticmethod
@@ -687,10 +703,12 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         ## Fixme: implement sort
         return self.person_map.keys()
 
-    def get_family_handles(self):
+    def get_family_handles(self, sort_handles=False):
+        ## Fixme: implement sort
         return self.family_map.keys()
 
     def get_event_handles(self, sort_handles=False):
+        ## Fixme: implement sort
         return self.event_map.keys()
 
     def get_citation_handles(self, sort_handles=False):
@@ -705,78 +723,80 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         ## Fixme: implement sort
         return self.place_map.keys()
 
-    def get_repository_handles(self):
+    def get_repository_handles(self, sort_handles=False):
+        ## Fixme: implement sort
         return self.repository_map.keys()
 
     def get_media_object_handles(self, sort_handles=False):
         ## Fixme: implement sort
         return self.media_map.keys()
 
-    def get_note_handles(self):
+    def get_note_handles(self, sort_handles=False):
+        ## Fixme: implement sort
         return self.note_map.keys()
 
     def get_tag_handles(self, sort_handles=False):
-        # FIXME: sort
+        # FIXME: implement sort
         return self.tag_map.keys()
 
     def get_event_from_handle(self, handle):
         event = None
         if handle in self.event_map:
-            event = self.event_map[handle]
+            event = Event.create(self.event_map[handle])
         return event
 
     def get_family_from_handle(self, handle): 
         family = None
         if handle in self.family_map:
-            family = self.family_map[handle]
+            family = Family.create(self.family_map[handle])
         return family
 
     def get_repository_from_handle(self, handle):
         repository = None
         if handle in self.repository_map:
-            repository = self.repository_map[handle]
+            repository = Repository.create(self.repository_map[handle])
         return repository
 
     def get_person_from_handle(self, handle):
         person = None
         if handle in self.person_map:
-            person = self.person_map[handle]
+            person = Person.create(self.person_map[handle])
         return person
 
     def get_place_from_handle(self, handle):
         place = None
         if handle in self.place_map:
-            place = self.place_map[handle]
+            place = Place.create(self.place_map[handle])
         return place
 
     def get_citation_from_handle(self, handle):
         citation = None
         if handle in self.citation_map:
-            citation = self.citation_map[handle]
+            citation = Citation.create(self.citation_map[handle])
         return citation
 
     def get_source_from_handle(self, handle):
         source = None
         if handle in self.source_map:
-            source = self.source_map[handle]
+            source = Source.create(self.source_map[handle])
         return source
 
     def get_note_from_handle(self, handle):
         note = None
         if handle in self.note_map:
-            note = self.note_map[handle]
+            note = Note.create(self.note_map[handle])
         return note
 
     def get_object_from_handle(self, handle):
         media = None
         if handle in self.media_map:
-            media = self.media_map[handle]
+            media = MediaObject.create(self.media_map[handle])
         return media
 
     def get_tag_from_handle(self, handle):
         tag = None
         if handle in self.tag_map:
-            tag = self.tag_map[handle]
+            tag = Tag.create(self.tag_map[handle])
         return tag
 
     def get_default_person(self):
@@ -787,81 +807,73 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             return None
 
     def iter_people(self):
-        return (person for person in self.person_map.values())
+        return (Person.create(person) for person in self.person_map.values())
 
     def iter_person_handles(self):
         return (handle for handle in self.person_map.keys())
 
     def iter_families(self):
-        return (family for family in self.family_map.values())
+        return (Family.create(family) for family in self.family_map.values())
 
     def iter_family_handles(self):
         return (handle for handle in self.family_map.keys())
 
     def get_tag_from_name(self, name):
-        for tag in self.tag_map.values():
+        ## Slow, but typically not too many tags:
+        for data in self.tag_map.values():
+            tag = Tag.create(data)
             if tag.name == name:
                 return tag
         return None
 
     def get_person_from_gramps_id(self, gramps_id):
-        for person in self.person_map.values():
-            if person.gramps_id == gramps_id:
-                return person
+        if gramps_id in self.person_id_map:
+            return Person.create(self.person_id_map[gramps_id])
         return None
 
     def get_family_from_gramps_id(self, gramps_id):
-        for family in self.family_map.values():
-            if family.gramps_id == gramps_id:
-                return family
+        if gramps_id in self.family_id_map:
+            return Family.create(self.family_id_map[gramps_id])
         return None
 
     def get_citation_from_gramps_id(self, gramps_id):
-        for citation in self.citation_map.values():
-            if citation.gramps_id == gramps_id:
-                return citation
+        if gramps_id in self.citation_id_map:
+            return Citation.create(self.citation_id_map[gramps_id])
         return None
 
     def get_source_from_gramps_id(self, gramps_id):
-        for source in self.source_map.values():
-            if source.gramps_id == gramps_id:
-                return source
+        if gramps_id in self.source_id_map:
+            return Source.create(self.source_id_map[gramps_id])
         return None
 
     def get_event_from_gramps_id(self, gramps_id):
-        for event in self.event_map.values():
-            if event.gramps_id == gramps_id:
-                return event
+        if gramps_id in self.event_id_map:
+            return Event.create(self.event_id_map[gramps_id])
         return None
 
     def get_media_from_gramps_id(self, gramps_id):
-        for media in self.media_map.values():
-            if media.gramps_id == gramps_id:
-                return media
+        if gramps_id in self.media_id_map:
+            return MediaObject.create(self.media_id_map[gramps_id])
         return None
 
     def get_place_from_gramps_id(self, gramps_id):
-        for place in self.place_map.values():
-            if place.gramps_id == gramps_id:
-                return place
+        if gramps_id in self.place_id_map:
+            return Place.create(self.place_id_map[gramps_id])
         return None
 
     def get_repository_from_gramps_id(self, gramps_id):
-        for repository in self.repository_map.values():
-            if repository.gramps_id == gramps_id:
-                return repository
+        if gramps_id in self.repository_id_map:
+            return Repository.create(self.repository_id_map[gramps_id])
         return None
 
     def get_note_from_gramps_id(self, gramps_id):
-        for note in self.note_map.values():
-            if note.gramps_id == gramps_id:
-                return note
+        if gramps_id in self.note_id_map:
+            return Note.create(self.note_id_map[gramps_id])
         return None
 
     def get_tag_from_gramps_id(self, gramps_id):
-        for tag in self.tag_map.values():
-            if tag.gramps_id == gramps_id:
-                return tag
+        if gramps_id in self.tag_id_map:
+            return Tag.create(self.tag_id_map[gramps_id])
         return None
 
     def get_number_of_people(self):
@@ -874,7 +886,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return len(self.place_map)
 
     def get_number_of_tags(self):
-        return 0 # FIXME
+        return len(self.tag_map)
 
     def get_number_of_families(self):
         return len(self.family_map)
@@ -895,52 +907,48 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return len(self.repository_map)
 
     def get_place_cursor(self):
-        return Cursor(self.place_map, self.get_raw_place_data)
+        return Cursor(self.place_map)
 
     def get_person_cursor(self):
-        return Cursor(self.person_map, self.get_raw_person_data)
+        return Cursor(self.person_map)
 
     def get_family_cursor(self):
-        return Cursor(self.family_map, self.get_raw_family_data)
+        return Cursor(self.family_map)
 
     def get_event_cursor(self):
-        return Cursor(self.event_map, self.get_raw_event_data)
+        return Cursor(self.event_map)
 
     def get_note_cursor(self):
-        return Cursor(self.note_map, self.get_raw_note_data)
+        return Cursor(self.note_map)
 
     def get_tag_cursor(self):
-        return Cursor(self.tag_map, self.get_raw_tag_data)
+        return Cursor(self.tag_map)
 
     def get_repository_cursor(self):
-        return Cursor(self.repository_map, self.get_raw_repository_data)
+        return Cursor(self.repository_map)
 
     def get_media_cursor(self):
-        return Cursor(self.media_map, self.get_raw_object_data)
+        return Cursor(self.media_map)
 
     def get_citation_cursor(self):
-        return Cursor(self.citation_map, self.get_raw_citation_data)
+        return Cursor(self.citation_map)
 
     def get_source_cursor(self):
-        return Cursor(self.source_map, self.get_raw_source_data)
+        return Cursor(self.source_map)
 
     def has_gramps_id(self, obj_key, gramps_id):
         key2table = {
-            PERSON_KEY:     self.person_map, 
-            FAMILY_KEY:     self.family_map, 
-            SOURCE_KEY:     self.source_map, 
-            CITATION_KEY:   self.citation_map, 
-            EVENT_KEY:      self.event_map, 
-            MEDIA_KEY:      self.media_map, 
-            PLACE_KEY:      self.place_map, 
-            REPOSITORY_KEY: self.repository_map, 
-            NOTE_KEY:       self.note_map, 
+            PERSON_KEY:     self.person_id_map, 
+            FAMILY_KEY:     self.family_id_map, 
+            SOURCE_KEY:     self.source_id_map, 
+            CITATION_KEY:   self.citation_id_map, 
+            EVENT_KEY:      self.event_id_map, 
+            MEDIA_KEY:      self.media_id_map, 
+            PLACE_KEY:      self.place_id_map, 
+            REPOSITORY_KEY: self.repository_id_map, 
+            NOTE_KEY:       self.note_id_map, 
             }
-        map = key2table[obj_key]
-        for item in map.values():
-            if item.gramps_id == gramps_id:
-                return True
-        return False
+        return gramps_id in key2table[obj_key]
 
     def has_person_handle(self, handle):
         return handle in self.person_map
@@ -981,59 +989,61 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         pass
 
     def set_default_person_handle(self, handle):
+        ## FIXME
         pass
 
     def set_mediapath(self, mediapath):
+        ## FIXME
         pass
 
     def get_raw_person_data(self, handle):
         if handle in self.person_map:
-            return self.person_map[handle].serialize()
+            return self.person_map[handle]
         return None
 
     def get_raw_family_data(self, handle):
         if handle in self.family_map:
-            return self.family_map[handle].serialize()
+            return self.family_map[handle]
         return None
 
     def get_raw_citation_data(self, handle):
         if handle in self.citation_map:
-            return self.citation_map[handle].serialize()
+            return self.citation_map[handle]
         return None
 
     def get_raw_source_data(self, handle):
         if handle in self.source_map:
-            return self.source_map[handle].serialize()
+            return self.source_map[handle]
         return None
 
     def get_raw_repository_data(self, handle):
         if handle in self.repository_map:
-            return self.repository_map[handle].serialize()
+            return self.repository_map[handle]
         return None
 
     def get_raw_note_data(self, handle):
         if handle in self.note_map:
-            return self.note_map[handle].serialize()
+            return self.note_map[handle]
         return None
 
     def get_raw_place_data(self, handle):
         if handle in self.place_map:
-            return self.place_map[handle].serialize()
+            return self.place_map[handle]
         return None
 
     def get_raw_object_data(self, handle):
         if handle in self.media_map:
-            return self.media_map[handle].serialize()
+            return self.media_map[handle]
         return None
 
     def get_raw_tag_data(self, handle):
         if handle in self.tag_map:
-            return self.tag_map[handle].serialize()
+            return self.tag_map[handle]
         return None
 
     def get_raw_event_data(self, handle):
         if handle in self.event_map:
-            return self.event_map[handle].serialize()
+            return self.event_map[handle]
         return None
 
     def add_person(self, person, trans, set_gid=True):
@@ -1127,7 +1137,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "person-update"
             else:
                 emit = "person-add"
-        self.person_map[person.handle] = person
+        self.person_map[person.handle] = person.serialize()
+        self.person_id_map[person.gramps_id] = self.person_map[person.handle]
         # Emit after added:
         if emit:
             self.emit(emit, ([person.handle],))
@@ -1139,7 +1150,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "family-update"
             else:
                 emit = "family-add"
-        self.family_map[family.handle] = family
+        self.family_map[family.handle] = family.serialize()
+        self.family_id_map[family.gramps_id] = self.family_map[family.handle]
         # Emit after added:
         if emit:
             self.emit(emit, ([family.handle],))
@@ -1151,7 +1163,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "citation-update"
             else:
                 emit = "citation-add"
-        self.citation_map[citation.handle] = citation
+        self.citation_map[citation.handle] = citation.serialize()
+        self.citation_id_map[citation.gramps_id] = self.citation_map[citation.handle]
         # Emit after added:
         if emit:
             self.emit(emit, ([citation.handle],))
@@ -1163,7 +1176,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "source-update"
             else:
                 emit = "source-add"
-        self.source_map[source.handle] = source
+        self.source_map[source.handle] = source.serialize()
+        self.source_id_map[source.gramps_id] = self.source_map[source.handle]
         # Emit after added:
         if emit:
             self.emit(emit, ([source.handle],))
@@ -1175,7 +1189,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "repository-update"
             else:
                 emit = "repository-add"
-        self.repository_map[repository.handle] = repository
+        self.repository_map[repository.handle] = repository.serialize()
+        self.repository_id_map[repository.gramps_id] = self.repository_map[repository.handle]
         # Emit after added:
         if emit:
             self.emit(emit, ([repository.handle],))
@@ -1187,7 +1202,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "note-update"
             else:
                 emit = "note-add"
-        self.note_map[note.handle] = note
+        self.note_map[note.handle] = note.serialize()
+        self.note_id_map[note.gramps_id] = self.note_map[note.handle]
         # Emit after added:
         if emit:
             self.emit(emit, ([note.handle],))
@@ -1199,7 +1215,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "place-update"
             else:
                 emit = "place-add"
-        self.place_map[place.handle] = place
+        self.place_map[place.handle] = place.serialize()
+        self.place_id_map[place.gramps_id] = self.place_map[place.handle]
         # Emit after added:
         if emit:
             self.emit(emit, ([place.handle],))
@@ -1211,7 +1228,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "event-update"
             else:
                 emit = "event-add"
-        self.event_map[event.handle] = event
+        self.event_map[event.handle] = event.serialize()
+        self.event_id_map[event.gramps_id] = self.event_map[event.handle]
         # Emit after added:
         if emit:
             self.emit(emit, ([event.handle],))
@@ -1223,7 +1241,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "tag-update"
             else:
                 emit = "tag-add"
-        self.tag_map[tag.handle] = tag
+        self.tag_map[tag.handle] = tag.serialize()
+        self.tag_id_map[tag.gramps_id] = self.tag_map[tag.handle]
         # Emit after added:
         if emit:
             self.emit(emit, ([tag.handle],))
@@ -1235,34 +1254,35 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "media-update"
             else:
                 emit = "media-add"
-        self.media_map[media.handle] = media
+        self.media_map[media.handle] = media.serialize()
+        self.media_id_map[media.gramps_id] = self.media_map[media.handle]
         # Emit after added:
         if emit:
             self.emit(emit, ([media.handle],))
 
     def get_gramps_ids(self, obj_key):
         key2table = {
-            PERSON_KEY:     self.person_map, 
-            FAMILY_KEY:     self.family_map, 
-            CITATION_KEY:   self.citation_map, 
-            SOURCE_KEY:     self.source_map, 
-            EVENT_KEY:      self.event_map, 
-            MEDIA_KEY:      self.media_map, 
-            PLACE_KEY:      self.place_map, 
-            REPOSITORY_KEY: self.repository_map, 
-            NOTE_KEY:       self.note_map, 
+            PERSON_KEY:     self.person_id_map, 
+            FAMILY_KEY:     self.family_id_map, 
+            CITATION_KEY:   self.citation_id_map, 
+            SOURCE_KEY:     self.source_id_map, 
+            EVENT_KEY:      self.event_id_map, 
+            MEDIA_KEY:      self.media_id_map, 
+            PLACE_KEY:      self.place_id_map, 
+            REPOSITORY_KEY: self.repository_id_map, 
+            NOTE_KEY:       self.note_id_map, 
             }
-        table = key2table[obj_key]
-        return [item.gramps_id for item in table.values()]
+        return list(key2table[obj_key].keys())
 
     def transaction_begin(self, transaction):
+        ## FIXME
         return 
 
-    def disable_signals(self):
-        pass
-
     def set_researcher(self, owner):
-        pass
+        self.owner.set_from(owner)
+
+    def get_researcher(self):
+        return self.owner
 
     def request_rebuild(self):
         self.emit('person-rebuild')
@@ -1332,7 +1352,9 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if self.readonly or not handle:
             return
         if handle in self.person_map:
+            person = Person.create(self.person_map[handle])
             del self.person_map[handle]
+            del self.person_id_map[person.gramps_id]
             self.emit("person-delete", ([handle],))
 
     def remove_source(self, handle, transaction):
@@ -1341,7 +1363,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         database, preserving the change in the passed transaction. 
         """
         self.__do_remove(handle, transaction, self.source_map, 
-                              SOURCE_KEY)
+                         self.source_id_map, SOURCE_KEY)
 
     def remove_citation(self, handle, transaction):
         """
@@ -1349,7 +1371,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         database, preserving the change in the passed transaction. 
         """
         self.__do_remove(handle, transaction, self.citation_map, 
-                              CITATION_KEY)
+                         self.citation_id_map, CITATION_KEY)
 
     def remove_event(self, handle, transaction):
         """
@@ -1357,7 +1379,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         database, preserving the change in the passed transaction. 
         """
         self.__do_remove(handle, transaction, self.event_map, 
-                              EVENT_KEY)
+                         self.event_id_map, EVENT_KEY)
 
     def remove_object(self, handle, transaction):
         """
@@ -1365,7 +1387,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         database, preserving the change in the passed transaction. 
         """
         self.__do_remove(handle, transaction, self.media_map, 
-                              MEDIA_KEY)
+                         self.media_id_map, MEDIA_KEY)
 
     def remove_place(self, handle, transaction):
         """
@@ -1373,7 +1395,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         database, preserving the change in the passed transaction. 
         """
         self.__do_remove(handle, transaction, self.place_map, 
-                              PLACE_KEY)
+                         self.place_id_map, PLACE_KEY)
 
     def remove_family(self, handle, transaction):
         """
@@ -1381,7 +1403,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         database, preserving the change in the passed transaction. 
         """
         self.__do_remove(handle, transaction, self.family_map, 
-                              FAMILY_KEY)
+                         self.family_id_map, FAMILY_KEY)
 
     def remove_repository(self, handle, transaction):
         """
@@ -1389,7 +1411,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         database, preserving the change in the passed transaction. 
         """
         self.__do_remove(handle, transaction, self.repository_map, 
-                              REPOSITORY_KEY)
+                         self.repository_id_map, REPOSITORY_KEY)
 
     def remove_note(self, handle, transaction):
         """
@@ -1397,7 +1419,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         database, preserving the change in the passed transaction. 
         """
         self.__do_remove(handle, transaction, self.note_map, 
-                              NOTE_KEY)
+                         self.note_id_map, NOTE_KEY)
 
     def remove_tag(self, handle, transaction):
         """
@@ -1405,7 +1427,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         database, preserving the change in the passed transaction. 
         """
         self.__do_remove(handle, transaction, self.tag_map, 
-                              TAG_KEY)
+                         self.tag_id_map, TAG_KEY)
 
     def is_empty(self):
         """
@@ -1416,13 +1438,13 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 return False
         return True
 
-    def __do_remove(self, handle, transaction, data_map, key):
+    def __do_remove(self, handle, transaction, data_map, data_id_map, key):
         if self.readonly or not handle:
             return
-        if isinstance(handle, str):
-            handle = handle.encode('utf-8')
         if handle in data_map:
+            obj = self._tables[KEY_TO_CLASS_MAP[key]]["class_func"].create(data_map[handle])
             del data_map[handle]
+            del data_id_map[obj.gramps_id]
             self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],))
 
     def delete_primary_from_reference_map(self, handle, transaction, txn=None):
@@ -1486,6 +1508,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     ## Missing:
 
     def backup(self):
+        ## FIXME
         pass
 
     def close(self):
@@ -1499,6 +1522,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             touch(filename)
 
     def find_backlink_handles(self, handle, include_classes=None):
+        ## FIXME
         return []
 
     def find_initial_person(self):
@@ -1508,22 +1532,30 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return None
 
     def find_place_child_handles(self, handle):
+        ## FIXME
         return []
 
     def get_bookmarks(self):
         return self.bookmarks
 
     def get_child_reference_types(self):
+        ## FIXME
         return []
 
     def get_citation_bookmarks(self):
         return self.citation_bookmarks
 
     def get_cursor(self, table, txn=None, update=False, commit=False):
+        ## FIXME
+        ## called from a complete find_back_ref
         pass
 
-    def get_dbname(self):
-        return "DictionaryDb"
+    # cursors for lookups in the reference_map for back reference
+    # lookups. The reference_map has three indexes:
+    # the main index: a tuple of (primary_handle, referenced_handle)
+    # the primary_handle index: the primary_handle
+    # the referenced_handle index: the referenced_handle
+    # the main index is unique, the others allow duplicate entries.
 
     def get_default_handle(self):
         items = self.person_map.keys()
@@ -1532,124 +1564,141 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return None
 
     def get_event_attribute_types(self):
+        ## FIXME
         return []
 
     def get_event_bookmarks(self):
         return self.event_bookmarks
 
     def get_event_roles(self):
+        ## FIXME
         return []
 
     def get_event_types(self):
+        ## FIXME
         return []
 
     def get_family_attribute_types(self):
+        ## FIXME
         return []
 
     def get_family_bookmarks(self):
         return self.family_bookmarks
 
     def get_family_event_types(self):
+        ## FIXME
         return []
 
     def get_family_relation_types(self):
+        ## FIXME
         return []
 
     def get_media_attribute_types(self):
+        ## FIXME
         return []
 
     def get_media_bookmarks(self):
         return self.media_bookmarks
 
     def get_name_types(self):
+        ## FIXME
         return []
 
     def get_note_bookmarks(self):
         return self.note_bookmarks
 
     def get_note_types(self):
+        ## FIXME
         return []
 
-    def get_number_of_records(self, table):
-        return 0
-
     def get_origin_types(self):
+        ## FIXME
         return []
 
     def get_person_attribute_types(self):
+        ## FIXME
         return []
 
     def get_person_event_types(self):
+        ## FIXME
         return []
 
     def get_place_bookmarks(self):
         return self.place_bookmarks
 
     def get_place_tree_cursor(self):
+        ## FIXME
         return []
 
     def get_place_types(self):
+        ## FIXME
         return []
 
     def get_repo_bookmarks(self):
         return self.repo_bookmarks
 
     def get_repository_types(self):
+        ## FIXME
         return []
 
     def get_save_path(self):
         return self._directory
 
     def get_source_attribute_types(self):
+        ## FIXME
         return []
 
     def get_source_bookmarks(self):
         return self.source_bookmarks
 
     def get_source_media_types(self):
+        ## FIXME
         return []
 
     def get_surname_list(self):
+        ## FIXME
         return []
 
     def get_url_types(self):
+        ## FIXME
         return []
 
     def has_changed(self):
+        ## FIXME
         return True
 
     def is_open(self):
-        return True
+        return self._directory is not None
 
     def iter_citation_handles(self):
         return (key for key in self.citation_map.keys())
 
     def iter_citations(self):
-        return (key for key in self.citation_map.values())
+        return (Citation.create(key) for key in self.citation_map.values())
 
     def iter_event_handles(self):
         return (key for key in self.event_map.keys())
 
     def iter_events(self):
-        return (key for key in self.event_map.values())
+        return (Events.create(key) for key in self.event_map.values())
 
     def iter_media_objects(self):
-        return (key for key in self.media_map.values())
+        return (MediaObject.create(key) for key in self.media_map.values())
 
     def iter_note_handles(self):
         return (key for key in self.note_map.keys())
 
     def iter_notes(self):
-        return (key for key in self.note_map.values())
+        return (Note.create(key) for key in self.note_map.values())
 
     def iter_place_handles(self):
         return (key for key in self.place_map.keys())
 
     def iter_places(self):
-        return (key for key in self.place_map.values())
+        return (Place.create(key) for key in self.place_map.values())
 
     def iter_repositories(self):
-        return (key for key in self.repositories_map.values())
+        return (Repository.create(key) for key in self.repositories_map.values())
 
     def iter_repository_handles(self):
         return (key for key in self.repositories_map.keys())
@@ -1658,13 +1707,13 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return (key for key in self.source_map.keys())
 
     def iter_sources(self):
-        return (key for key in self.source_map.values())
+        return (Source.create(key) for key in self.source_map.values())
 
     def iter_tag_handles(self):
         return (key for key in self.tag_map.keys())
 
     def iter_tags(self):
-        return (key for key in self.tag_map.values())
+        return (Tag.create(key) for key in self.tag_map.values())
 
     def load(self, directory, pulse_progress=None, mode=None, 
              force_schema_upgrade=False, 
@@ -1682,16 +1731,20 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             importData(self, filename, User())
 
     def prepare_import(self):
+        ## FIXME
         pass
 
     def redo(self, update_history=True):
+        ## FIXME
         pass
 
     def restore(self):
+        ## FIXME
         pass
 
     def set_prefixes(self, person, media, family, source, citation, 
                      place, event, repository, note):
+        ## FIXME
         pass
 
     def set_save_path(self, directory):
@@ -1701,6 +1754,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.brief_name = os.path.basename(self._directory)
 
     def undo(self, update_history=True):
+        ## FIXME
         pass
 
     def write_version(self, directory):
@@ -1749,3 +1803,10 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             name = None
         return name
 
+    def reindex_reference_map(self):
+        ## FIXME
+        pass
+
+    def rebuild_secondary(self, update):
+        ## FIXME
+        pass
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index 182295645..f0d1faf44 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -44,7 +44,7 @@ from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
 from gramps.gen.lib import (Person, Family, Event, Place, Repository, 
                             Citation, Source, Note, MediaObject, Tag, 
-                            Researcher)
+                            Researcher, GenderStats)
 from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn
 from gramps.gen.db.undoredo import DbUndo
 from gramps.gen.utils.callback import Callback
@@ -352,6 +352,8 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.full_name = None
         self.path = None
         self.brief_name = None
+        self.genderStats = GenderStats() # can pass in loaded stats as dict
+        self.owner = Researcher()
         if directory:
             self.load(directory)
 
@@ -788,8 +790,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return None
 
     def get_researcher(self):
-        obj = Researcher()
-        return obj
+        return self.owner
 
     def get_tag_handles(self, sort_handles=False):
         if sort_handles:
@@ -1704,7 +1705,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return 
 
     def set_researcher(self, owner):
-        pass
+        self.owner.set_from(owner)
 
     def copy_from_db(self, db):
         """
@@ -2037,9 +2038,10 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def remove_from_surname_list(self, person):
         ## FIXME
+        ## called by a complete commit_person
         pass
     
-    ## missing
+    ## was missing
 
     def find_place_child_handles(self, handle):
         pass
@@ -2146,3 +2148,8 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             name = None
         return name
 
+    def reindex_reference_map(self):
+        pass
+
+    def rebuild_secondary(self, update):
+        pass

From d8346a705fd54920c43e6baaa9db01c33c6fb87c Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Fri, 15 May 2015 23:26:22 -0400
Subject: [PATCH 027/105] DjangoDb: always force a gramps_id; typo fix

---
 gramps/plugins/database/djangodb.py | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index f0d1faf44..ca55eb0b1 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -1429,7 +1429,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def add_person(self, person, trans, set_gid=True):
         if not person.handle:
             person.handle = create_id()
-        if not person.gramps_id and set_gid:
+        if not person.gramps_id:
             person.gramps_id = self.find_next_person_gramps_id()
         self.commit_person(person, trans)
         return person.handle
@@ -1437,7 +1437,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def add_family(self, family, trans, set_gid=True):
         if not family.handle:
             family.handle = create_id()
-        if not family.gramps_id and set_gid:
+        if not family.gramps_id:
             family.gramps_id = self.find_next_family_gramps_id()
         self.commit_family(family, trans)
         return family.handle
@@ -1445,7 +1445,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def add_citation(self, citation, trans, set_gid=True):
         if not citation.handle:
             citation.handle = create_id()
-        if not citation.gramps_id and set_gid:
+        if not citation.gramps_id:
             citation.gramps_id = self.find_next_citation_gramps_id()
         self.commit_citation(citation, trans)
         return citation.handle
@@ -1453,7 +1453,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def add_source(self, source, trans, set_gid=True):
         if not source.handle:
             source.handle = create_id()
-        if not source.gramps_id and set_gid:
+        if not source.gramps_id:
             source.gramps_id = self.find_next_source_gramps_id()
         self.commit_source(source, trans)
         return source.handle
@@ -1461,7 +1461,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def add_repository(self, repository, trans, set_gid=True):
         if not repository.handle:
             repository.handle = create_id()
-        if not repository.gramps_id and set_gid:
+        if not repository.gramps_id:
             repository.gramps_id = self.find_next_repository_gramps_id()
         self.commit_repository(repository, trans)
         return repository.handle
@@ -1469,7 +1469,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def add_note(self, note, trans, set_gid=True):
         if not note.handle:
             note.handle = create_id()
-        if not note.gramps_id and set_gid:
+        if not note.gramps_id:
             note.gramps_id = self.find_next_note_gramps_id()
         self.commit_note(note, trans)
         return note.handle
@@ -1477,7 +1477,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def add_place(self, place, trans, set_gid=True):
         if not place.handle:
             place.handle = create_id()
-        if not place.gramps_id and set_gid:
+        if not place.gramps_id:
             place.gramps_id = self.find_next_place_gramps_id()
         self.commit_place(place, trans)
         return place.handle
@@ -1485,7 +1485,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def add_event(self, event, trans, set_gid=True):
         if not event.handle:
             event.handle = create_id()
-        if not event.gramps_id and set_gid:
+        if not event.gramps_id:
             event.gramps_id = self.find_next_event_gramps_id()
         self.commit_event(event, trans)
         return event.handle
@@ -1493,7 +1493,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def add_tag(self, tag, trans):
         if not tag.handle:
             tag.handle = create_id()
-        self.commit_event(tag, trans)
+        self.commit_tag(tag, trans)
         return tag.handle
 
     def add_object(self, obj, transaction, set_gid=True):
@@ -1505,7 +1505,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         """
         if not obj.handle:
             obj.handle = create_id()
-        if not obj.gramps_id and set_gid:
+        if not obj.gramps_id:
             obj.gramps_id = self.find_next_object_gramps_id()
         self.commit_media_object(obj, transaction)
         return obj.handle
@@ -1664,7 +1664,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 else:
                     self.emit("tag-add", ([tag.handle],))
 
-    def commit_media_object(self, media, transaction, change_time=None):
+    def commit_media_object(self, media, trans, change_time=None):
         """
         Commit the specified MediaObject to the database, storing the changes
         as part of the transaction.

From 2147f721768feb4fe16d436cdd29042d7d39ddca Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 16 May 2015 00:16:33 -0400
Subject: [PATCH 028/105] Importers: added db.prepare_import/db.commit_import
 to wrap imports

---
 gramps/plugins/database/bsddb_support/write.py | 12 ++++++++++++
 gramps/plugins/database/dictionarydb.py        | 13 +++++++++++++
 gramps/plugins/database/djangodb.py            |  2 +-
 gramps/plugins/importer/importcsv.py           |  2 ++
 gramps/plugins/importer/importgedcom.py        |  2 ++
 gramps/plugins/importer/importgeneweb.py       |  2 ++
 gramps/plugins/importer/importgpkg.py          |  2 ++
 gramps/plugins/importer/importprogen.py        |  2 ++
 gramps/plugins/importer/importvcard.py         |  2 ++
 gramps/plugins/importer/importxml.py           |  2 ++
 gramps/webapp/reports.py                       |  4 ++--
 11 files changed, 42 insertions(+), 3 deletions(-)

diff --git a/gramps/plugins/database/bsddb_support/write.py b/gramps/plugins/database/bsddb_support/write.py
index cca39eae7..9630b3ce2 100644
--- a/gramps/plugins/database/bsddb_support/write.py
+++ b/gramps/plugins/database/bsddb_support/write.py
@@ -2470,6 +2470,18 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
             _("Version"): bsddb_version,
         }
 
+    def prepare_import(self):
+        """
+        Initialization before imports
+        """
+        pass
+
+    def commit_import(self):
+        """
+        Post process after imports
+        """
+        pass
+
 def mk_backup_name(database, base):
     """
     Return the backup name of the database table
diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 38ad398ac..44e3721fa 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -1810,3 +1810,16 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def rebuild_secondary(self, update):
         ## FIXME
         pass
+
+    def prepare_import(self):
+        """
+        Initialization before imports
+        """
+        pass
+
+    def commit_import(self):
+        """
+        Post process after imports
+        """
+        pass
+
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index ca55eb0b1..f7fe47892 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -518,7 +518,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 self.dji.add_tag_detail(obj.serialize())
         self.use_import_cache = False
         self.import_cache = {}
-        self.dji.update_publics()
+        self.request_rebuild()
 
     def transaction_commit(self, txn):
         pass
diff --git a/gramps/plugins/importer/importcsv.py b/gramps/plugins/importer/importcsv.py
index 2b25a588f..c39362425 100644
--- a/gramps/plugins/importer/importcsv.py
+++ b/gramps/plugins/importer/importcsv.py
@@ -151,8 +151,10 @@ def importData(dbase, filename, user):
         parser = CSVParser(dbase, user, (config.get('preferences.tag-on-import-format') if 
                                          config.get('preferences.tag-on-import') else None))
     try:
+        dbase.prepare_import()
         with OpenFileOrStdin(filename, 'b') as filehandle:
             parser.parse(filehandle)
+        dbase.commit_import()
     except EnvironmentError as err:
         user.notify_error(_("%s could not be opened\n") % filename, str(err))
         return
diff --git a/gramps/plugins/importer/importgedcom.py b/gramps/plugins/importer/importgedcom.py
index da257a764..72db2d29d 100644
--- a/gramps/plugins/importer/importgedcom.py
+++ b/gramps/plugins/importer/importgedcom.py
@@ -130,7 +130,9 @@ def importData(database, filename, user):
     try:
         read_only = database.readonly
         database.readonly = False
+        database.prepare_import()
         gedparse.parse_gedcom_file(False)
+        database.commit_import()
         database.readonly = read_only
         ifile.close()
     except IOError as msg:
diff --git a/gramps/plugins/importer/importgeneweb.py b/gramps/plugins/importer/importgeneweb.py
index 01707577a..b4505efd3 100644
--- a/gramps/plugins/importer/importgeneweb.py
+++ b/gramps/plugins/importer/importgeneweb.py
@@ -86,7 +86,9 @@ def importData(database, filename, user):
         return
 
     try:
+        database.prepare_import()
         status = g.parse_geneweb_file()
+        database.commit_import()
     except IOError as msg:
         errmsg = _("%s could not be opened\n") % filename
         user.notify_error(errmsg,str(msg))
diff --git a/gramps/plugins/importer/importgpkg.py b/gramps/plugins/importer/importgpkg.py
index 3c7f1b4c7..2a164b09d 100644
--- a/gramps/plugins/importer/importgpkg.py
+++ b/gramps/plugins/importer/importgpkg.py
@@ -93,7 +93,9 @@ def impData(database, name, user):
     imp_db_name = os.path.join(tmpdir_path, XMLFILE)  
 
     importer = importData
+    database.prepare_import()
     info = importer(database, imp_db_name, user)
+    database.commit_import()
 
     newmediapath = database.get_mediapath()
     #import of gpkg should not change media path as all media has new paths!
diff --git a/gramps/plugins/importer/importprogen.py b/gramps/plugins/importer/importprogen.py
index 584a3281e..202f56118 100644
--- a/gramps/plugins/importer/importprogen.py
+++ b/gramps/plugins/importer/importprogen.py
@@ -74,7 +74,9 @@ def _importData(database, filename, user):
         return
 
     try:
+        database.prepare_import()
         status = g.parse_progen_file()
+        database.commit_import()
     except ProgenError as msg:
         user.notify_error(_("Pro-Gen data error"), str(msg))
         return
diff --git a/gramps/plugins/importer/importvcard.py b/gramps/plugins/importer/importvcard.py
index 15cf804e5..4d504931f 100644
--- a/gramps/plugins/importer/importvcard.py
+++ b/gramps/plugins/importer/importvcard.py
@@ -62,8 +62,10 @@ def importData(database, filename, user):
     """Function called by Gramps to import data on persons in VCard format."""
     parser = VCardParser(database)
     try:
+        database.prepare_import()
         with OpenFileOrStdin(filename) as filehandle:
             parser.parse(filehandle)
+        database.commit_import()
     except EnvironmentError as msg:
         user.notify_error(_("%s could not be opened\n") % filename, str(msg))
         return
diff --git a/gramps/plugins/importer/importxml.py b/gramps/plugins/importer/importxml.py
index 51525dbd6..21ce6a7a0 100644
--- a/gramps/plugins/importer/importxml.py
+++ b/gramps/plugins/importer/importxml.py
@@ -122,6 +122,7 @@ def importData(database, filename, user):
     line_cnt = 0
     person_cnt = 0
     
+    database.prepare_import()
     with ImportOpenFileContextManager(filename, user) as xml_file:
         if xml_file is None:
             return
@@ -162,6 +163,7 @@ def importData(database, filename, user):
                           "valid Gramps database."))
             return
 
+    database.commit_import()
     database.readonly = read_only
     return info
 
diff --git a/gramps/webapp/reports.py b/gramps/webapp/reports.py
index c0c7f480e..35cc66356 100644
--- a/gramps/webapp/reports.py
+++ b/gramps/webapp/reports.py
@@ -85,9 +85,9 @@ def import_file(db, filename, user):
                     print("ERROR:", name, exception)
                 return False
             import_function = getattr(mod, pdata.import_function)
-            db.prepare_import()
+            #db.prepare_import()
             retval = import_function(db, filename, user)
-            db.commit_import()
+            #db.commit_import()
             return retval
     return False
 

From bb9403e2b2f4fc97b1a558db8aa38be5d31f73ab Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 16 May 2015 00:46:22 -0400
Subject: [PATCH 029/105] DjangoDb: typo, added logger

---
 gramps/gen/dbstate.py               | 11 +++++++++++
 gramps/plugins/database/djangodb.py |  2 +-
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py
index 0d9c30d4a..85722464d 100644
--- a/gramps/gen/dbstate.py
+++ b/gramps/gen/dbstate.py
@@ -29,6 +29,14 @@ from .proxy.proxybase import ProxyDbBase
 from .utils.callback import Callback
 from .config import config
 
+#-------------------------------------------------------------------------
+#
+# set up logging
+#
+#-------------------------------------------------------------------------
+import logging
+LOG = logging.getLogger(".dbstate")
+
 class DbState(Callback):
     """
     Provide a class to encapsulate the state of the database.
@@ -152,6 +160,7 @@ class DbState(Callback):
 
     ## Work-around for databases that need sys refresh (django):
     def modules_is_set(self):
+        LOG.warn("modules_is_set?")
         if hasattr(self, "_modules"):
             return self._modules != None
         else:
@@ -159,6 +168,7 @@ class DbState(Callback):
             return False
 
     def reset_modules(self):
+        LOG.warn("reset_modules!")
         # First, clear out old modules:
         for key in list(sys.modules.keys()):
             del(sys.modules[key])
@@ -167,5 +177,6 @@ class DbState(Callback):
             sys.modules[key] = self._modules[key]
 
     def save_modules(self):
+        LOG.warn("save_modules!")
         self._modules = sys.modules.copy()
 
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index f7fe47892..c28cc9265 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -1670,7 +1670,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         as part of the transaction.
         """
         if self.use_import_cache:
-            self.import_cache[obj.handle] = media
+            self.import_cache[media.handle] = media
         else:
             raw = media.serialize()
             items = self.dji.Media.filter(handle=media.handle)

From d3bd2b669207da575b91b76e1916aa39d32bae83 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 16 May 2015 01:40:05 -0400
Subject: [PATCH 030/105] DjangoDb: force load when write_version/create to
 make work with reset modules

---
 gramps/gen/dbstate.py               | 6 +++---
 gramps/plugins/database/djangodb.py | 6 ++++++
 2 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py
index 85722464d..9d1ce8071 100644
--- a/gramps/gen/dbstate.py
+++ b/gramps/gen/dbstate.py
@@ -160,7 +160,7 @@ class DbState(Callback):
 
     ## Work-around for databases that need sys refresh (django):
     def modules_is_set(self):
-        LOG.warn("modules_is_set?")
+        LOG.info("modules_is_set?")
         if hasattr(self, "_modules"):
             return self._modules != None
         else:
@@ -168,7 +168,7 @@ class DbState(Callback):
             return False
 
     def reset_modules(self):
-        LOG.warn("reset_modules!")
+        LOG.info("reset_modules!")
         # First, clear out old modules:
         for key in list(sys.modules.keys()):
             del(sys.modules[key])
@@ -177,6 +177,6 @@ class DbState(Callback):
             sys.modules[key] = self._modules[key]
 
     def save_modules(self):
-        LOG.warn("save_modules!")
+        LOG.info("save_modules!")
         self._modules = sys.modules.copy()
 
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index c28cc9265..cb8f70238 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -362,6 +362,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
              force_bsddb_upgrade=False,
              force_bsddb_downgrade=False,
              force_python_upgrade=False):
+        _LOG.info("Django loading...")
         self._directory = directory
         self.full_name = os.path.abspath(self._directory)
         self.path = self.full_name
@@ -380,9 +381,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             def __getattr__(self, item):
                 return self.dictionary[item]
 
+        _LOG.info("Django loading defaults from: " + directory)
         try:
             settings.configure(Module(default_settings))
         except RuntimeError:
+            _LOG.info("Django already configured error! Shouldn't happen!")
             # already configured; ignore
             pass
 
@@ -455,6 +458,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.url_types = set()
         self.media_attributes = set()
         self.place_types = set()
+        _LOG.info("Django loading... done!")
 
     def prepare_import(self):
         """
@@ -2108,6 +2112,8 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         for filename in os.listdir(defaults):
             fullpath = os.path.abspath(os.path.join(defaults, filename))
             shutil.copy2(fullpath, directory)
+        # force load, to get all modules loaded because of reset issue
+        self.load(directory)
 
     def report_bm_change(self):
         """

From 276052d231d7bbeae71d008b87c9399539d2cf8b Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 16 May 2015 02:15:02 -0400
Subject: [PATCH 031/105] Added missing function; allow failed plugin message
 to show

---
 gramps/gen/db/__init__.py | 21 +++++++++++++++++++++
 gramps/gui/viewmanager.py |  7 ++++++-
 2 files changed, 27 insertions(+), 1 deletion(-)

diff --git a/gramps/gen/db/__init__.py b/gramps/gen/db/__init__.py
index 066571e38..10f751303 100644
--- a/gramps/gen/db/__init__.py
+++ b/gramps/gen/db/__init__.py
@@ -88,3 +88,24 @@ from .base import *
 from .dbconst import *
 from .txn import *
 from .exceptions import *
+
+def find_surname_name(key, data):
+    """
+    Creating a surname from raw name, to use for sort and index
+    returns a byte string
+    """
+    return __index_surname(data[5])
+
+def __index_surname(surn_list):
+    """
+    All non pa/matronymic surnames are used in indexing.
+    pa/matronymic not as they change for every generation!
+    returns a byte string
+    """
+    from gramps.gen.lib import NameOriginType
+    if surn_list:
+        surn = " ".join([x[0] for x in surn_list if not (x[3][0] in [
+                    NameOriginType.PATRONYMIC, NameOriginType.MATRONYMIC]) ])
+    else:
+        surn = ""
+    return surn
diff --git a/gramps/gui/viewmanager.py b/gramps/gui/viewmanager.py
index a296f0122..a595eb803 100644
--- a/gramps/gui/viewmanager.py
+++ b/gramps/gui/viewmanager.py
@@ -1591,6 +1591,11 @@ def run_plugin(pdata, dbstate, uistate):
     mod = pmgr.load_plugin(pdata)
     if not mod:
         #import of plugin failed
+        failed = pmgr.get_fail_list()
+        if failed:
+            error_msg = failed[-1][1][1]
+        else:
+            error_msg = "(no error message)"
         ErrorDialog(
             _('Failed Loading Plugin'),
             _('The plugin %(name)s did not load and reported an error.\n\n'
@@ -1605,7 +1610,7 @@ def run_plugin(pdata, dbstate, uistate):
                 'gramps_bugtracker_url' : URL_BUGHOME,
                 'firstauthoremail': pdata.authors_email[0] if
                         pdata.authors_email else '...',
-                'error_msg': pmgr.get_fail_list()[-1][1][1]})
+                  'error_msg': error_msg})
         return
 
     if pdata.ptype == REPORT:

From acc6a652ebbe3ec785a504bceace806904b65573 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 16 May 2015 09:18:41 -0400
Subject: [PATCH 032/105] Removed duplicate methods

---
 gramps/plugins/database/dictionarydb.py |  8 --------
 gramps/plugins/database/djangodb.py     | 21 +++++----------------
 2 files changed, 5 insertions(+), 24 deletions(-)

diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 44e3721fa..878afa880 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -695,10 +695,6 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def get_name_group_mapping(self, key):
         return None
 
-    def get_researcher(self):
-        obj = Researcher()
-        return obj
-
     def get_person_handles(self, sort_handles=False):
         ## Fixme: implement sort
         return self.person_map.keys()
@@ -1730,10 +1726,6 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if os.path.isfile(filename):
             importData(self, filename, User())
 
-    def prepare_import(self):
-        ## FIXME
-        pass
-
     def redo(self, update_history=True):
         ## FIXME
         pass
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index cb8f70238..0091d033a 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -96,8 +96,10 @@ class Table(object):
         """
         return self.funcs["cursor_func"]()
 
-    def put(key, data, txn=None):
-        self[key] = data
+    def put(self, key, data, txn=None):
+        ## FIXME: probably needs implementing?
+        #self[key] = data
+        pass
 
 class Map(dict):
     """
@@ -196,6 +198,7 @@ class DjangoTxn(DbTxn):
     def put(self, handle, new_data, txn):
         """
         """
+        ## FIXME: probably not correct?
         txn[handle] = new_data
 
     def commit(self):
@@ -2053,20 +2056,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def get_cursor(self, table, txn=None, update=False, commit=False):
         pass
 
-    def get_from_name_and_handle(self, table_name, handle):
-        """
-        Returns a gen.lib object (or None) given table_name and
-        handle.
-
-        Examples:
-
-        >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008")
-        >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23")
-        """
-        if table_name in self._tables:
-            return self._tables[table_name]["handle_func"](handle)
-        return None
-
     def get_number_of_records(self, table):
         pass
 

From 92f435d45b15be2a758c387b1206d42cc6a91a5f Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sun, 17 May 2015 11:38:31 -0400
Subject: [PATCH 033/105] Diff: fixed import of DictionaryDb; removed mistaken
 tag.gramps_id references in DictionartDb

---
 gramps/gen/merge/diff.py                |  2 +-
 gramps/plugins/database/dictionarydb.py | 12 +++---------
 2 files changed, 4 insertions(+), 10 deletions(-)

diff --git a/gramps/gen/merge/diff.py b/gramps/gen/merge/diff.py
index 67539a0bb..a0d4bebad 100644
--- a/gramps/gen/merge/diff.py
+++ b/gramps/gen/merge/diff.py
@@ -28,7 +28,7 @@ from gramps.cli.user import User
 from ..dbstate import DbState
 from gramps.cli.grampscli import CLIManager
 from ..plug import BasePluginManager
-from ..db.dictionary import DictionaryDb
+from gramps.plugins.database.dictionarydb import DictionaryDb
 from gramps.gen.lib.handle import HandleClass, Handle
 from gramps.gen.lib import *
 from gramps.gen.lib.personref import PersonRef
diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 878afa880..549732b34 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -402,7 +402,6 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.event_map  = Map(Table(self._tables["Event"]))
         self.event_id_map = {}
         self.tag_map  = Map(Table(self._tables["Tag"]))
-        self.tag_id_map = {}
         self.metadata   = Map(Table({"cursor_func": lambda: MetaCursor()}))
         self.name_group = {}
         self.undo_callback = None
@@ -867,11 +866,6 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             return Note.create(self.note_id_map[gramps_id])
         return None
 
-    def get_tag_from_gramps_id(self, gramps_id):
-        if gramps_id in self.tag_id_map:
-            return Tag.create(self.tag_id_map[gramps_id])
-        return None
-
     def get_number_of_people(self):
         return len(self.person_map)
 
@@ -1238,7 +1232,6 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             else:
                 emit = "tag-add"
         self.tag_map[tag.handle] = tag.serialize()
-        self.tag_id_map[tag.gramps_id] = self.tag_map[tag.handle]
         # Emit after added:
         if emit:
             self.emit(emit, ([tag.handle],))
@@ -1423,7 +1416,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         database, preserving the change in the passed transaction. 
         """
         self.__do_remove(handle, transaction, self.tag_map, 
-                         self.tag_id_map, TAG_KEY)
+                         None, TAG_KEY)
 
     def is_empty(self):
         """
@@ -1440,7 +1433,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if handle in data_map:
             obj = self._tables[KEY_TO_CLASS_MAP[key]]["class_func"].create(data_map[handle])
             del data_map[handle]
-            del data_id_map[obj.gramps_id]
+            if data_id_map:
+                del data_id_map[obj.gramps_id]
             self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],))
 
     def delete_primary_from_reference_map(self, handle, transaction, txn=None):

From a9b7b43ffbe8240fb656c1a12a5be44e20c1f4b7 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sun, 17 May 2015 19:01:21 -0400
Subject: [PATCH 034/105] DictionaryDb: give handle in bytes, handle as str
 internally in dict

---
 gramps/plugins/database/dictionarydb.py | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 549732b34..2eda2460d 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -735,60 +735,80 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return self.tag_map.keys()
 
     def get_event_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
         event = None
         if handle in self.event_map:
             event = Event.create(self.event_map[handle])
         return event
 
     def get_family_from_handle(self, handle): 
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
         family = None
         if handle in self.family_map:
             family = Family.create(self.family_map[handle])
         return family
 
     def get_repository_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
         repository = None
         if handle in self.repository_map:
             repository = Repository.create(self.repository_map[handle])
         return repository
 
     def get_person_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
         person = None
         if handle in self.person_map:
             person = Person.create(self.person_map[handle])
         return person
 
     def get_place_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
         place = None
         if handle in self.place_map:
             place = Place.create(self.place_map[handle])
         return place
 
     def get_citation_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
         citation = None
         if handle in self.citation_map:
             citation = Citation.create(self.citation_map[handle])
         return citation
 
     def get_source_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
         source = None
         if handle in self.source_map:
             source = Source.create(self.source_map[handle])
         return source
 
     def get_note_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
         note = None
         if handle in self.note_map:
             note = Note.create(self.note_map[handle])
         return note
 
     def get_object_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
         media = None
         if handle in self.media_map:
             media = MediaObject.create(self.media_map[handle])
         return media
 
     def get_tag_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
         tag = None
         if handle in self.tag_map:
             tag = Tag.create(self.tag_map[handle])

From e1c33c4d3ec0beb4c4cec66796764c189f6f31a3 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Thu, 21 May 2015 10:51:36 -0400
Subject: [PATCH 035/105] Added DbState.open_database() for opening without
 DbManager

---
 gramps/gen/dbstate.py | 56 ++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 53 insertions(+), 3 deletions(-)

diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py
index 9d1ce8071..b232d612e 100644
--- a/gramps/gen/dbstate.py
+++ b/gramps/gen/dbstate.py
@@ -23,6 +23,8 @@
 Provide the database state class
 """
 import sys
+import os
+import io
 
 from .db import DbReadBase
 from .proxy.proxybase import ProxyDbBase
@@ -62,9 +64,10 @@ class DbState(Callback):
         Closes the existing db, and opens a new one.
         Retained for backward compatibility.
         """
-        self.emit('no-database', ())
-        self.db.close()
-        self.change_database_noclose(database)
+        if database:
+            self.emit('no-database', ())
+            self.db.close()
+            self.change_database_noclose(database)
 
     def change_database_noclose(self, database):
         """
@@ -158,6 +161,53 @@ class DbState(Callback):
             database = getattr(mod, pdata.databaseclass)
             return database()
 
+    def open_database(self, dbname, force_unlock=False, callback=None):
+        """
+        Open a database by name and return the database.
+        """
+        data = self.lookup_family_tree(dbname)
+        database = None
+        if data:
+            dbpath, locked, locked_by, backend = data
+            if (not locked) or (locked and force_unlock):
+                database = self.make_database(backend)
+                database.load(dbpath, callback=callback)
+        return database
+
+    def lookup_family_tree(self, dbname):
+        """
+        Find a Family Tree given its name, and return properties.
+        """
+        dbdir = os.path.expanduser(config.get('behavior.database-path'))
+        for dpath in os.listdir(dbdir):
+            dirpath = os.path.join(dbdir, dpath)
+            path_name = os.path.join(dirpath, "name.txt")
+            if os.path.isfile(path_name):
+                file = io.open(path_name, 'r', encoding='utf8')
+                name = file.readline().strip()
+                file.close()
+                if dbname == name:
+                    locked = False
+                    locked_by = None
+                    backend = None
+                    fname = os.path.join(dirpath, "database.txt")
+                    if os.path.isfile(fname):
+                        ifile = io.open(fname, 'r', encoding='utf8')
+                        backend = ifile.read().strip()
+                        ifile.close()
+                    else:
+                        backend = "bsddb"
+                    try:
+                        fname = os.path.join(dirpath, "lock")
+                        ifile = io.open(fname, 'r', encoding='utf8')
+                        locked_by = ifile.read().strip()
+                        locked = True
+                        ifile.close()
+                    except (OSError, IOError):
+                        pass
+                    return (dirpath, locked, locked_by, backend)
+        return None
+
     ## Work-around for databases that need sys refresh (django):
     def modules_is_set(self):
         LOG.info("modules_is_set?")

From 720664818f3c5c427c8e38a1db19d7951f5c8daf Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 23 May 2015 01:34:16 -0400
Subject: [PATCH 036/105] WIP: Added DB-API 2.0 interface; needs to load/save
 details from dbdir; currently using sqlite :memory: database. But could use
 any DB-API 2.0 compatible layers.

---
 gramps/plugins/database/dbapi.gpr.py    |   31 +
 gramps/plugins/database/dbapi.py        | 2231 +++++++++++++++++++++++
 gramps/plugins/database/dictionarydb.py |    2 +-
 3 files changed, 2263 insertions(+), 1 deletion(-)
 create mode 100644 gramps/plugins/database/dbapi.gpr.py
 create mode 100644 gramps/plugins/database/dbapi.py

diff --git a/gramps/plugins/database/dbapi.gpr.py b/gramps/plugins/database/dbapi.gpr.py
new file mode 100644
index 000000000..2749e7a50
--- /dev/null
+++ b/gramps/plugins/database/dbapi.gpr.py
@@ -0,0 +1,31 @@
+#
+# Gramps - a GTK+/GNOME based genealogy program
+#
+# Copyright (C) 2015 Douglas Blank <doug.blank@gmail.com>
+#
+# 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+plg = newplugin()
+plg.id    = 'dbapi'
+plg.name  = _("DB-API 2.0")
+plg.name_accell  = _("DB-_API 2.0")
+plg.description =  _("DB-API 2.0 Database Backend")
+plg.version = '1.0'
+plg.gramps_target_version = "4.2"
+plg.status = STABLE
+plg.fname = 'dbapi.py'
+plg.ptype = DATABASE
+plg.databaseclass = 'DBAPI'
diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
new file mode 100644
index 000000000..b5d61e5dc
--- /dev/null
+++ b/gramps/plugins/database/dbapi.py
@@ -0,0 +1,2231 @@
+#------------------------------------------------------------------------
+#
+# Python Modules
+#
+#------------------------------------------------------------------------
+import pickle
+import base64
+import time
+import re
+import os
+import logging
+
+#------------------------------------------------------------------------
+#
+# Gramps Modules
+#
+#------------------------------------------------------------------------
+import gramps
+from gramps.gen.const import GRAMPS_LOCALE as glocale
+_ = glocale.translation.gettext
+from gramps.gen.db import (DbReadBase, DbWriteBase, DbTxn, 
+                           KEY_TO_NAME_MAP, KEY_TO_CLASS_MAP)
+from gramps.gen.utils.callback import Callback
+from gramps.gen.updatecallback import UpdateCallback
+from gramps.gen.db.undoredo import DbUndo
+from gramps.gen.db.dbconst import *
+from gramps.gen.db import (PERSON_KEY,
+                           FAMILY_KEY,
+                           CITATION_KEY,
+                           SOURCE_KEY,
+                           EVENT_KEY,
+                           MEDIA_KEY,
+                           PLACE_KEY,
+                           REPOSITORY_KEY,
+                           NOTE_KEY,
+                           TAG_KEY)
+
+from gramps.gen.utils.id import create_id
+from gramps.gen.lib.researcher import Researcher
+from gramps.gen.lib.mediaobj import MediaObject
+from gramps.gen.lib.person import Person
+from gramps.gen.lib.family import Family
+from gramps.gen.lib.src import Source
+from gramps.gen.lib.citation import Citation
+from gramps.gen.lib.event import Event
+from gramps.gen.lib.place import Place
+from gramps.gen.lib.repo import Repository
+from gramps.gen.lib.note import Note
+from gramps.gen.lib.tag import Tag
+from gramps.gen.lib.genderstats import GenderStats
+
+_LOG = logging.getLogger(DBLOGNAME)
+
+def touch(fname, mode=0o666, dir_fd=None, **kwargs):
+    ## After http://stackoverflow.com/questions/1158076/implement-touch-using-python
+    flags = os.O_CREAT | os.O_APPEND
+    with os.fdopen(os.open(fname, flags=flags, mode=mode, dir_fd=dir_fd)) as f:
+        os.utime(f.fileno() if os.utime in os.supports_fd else fname,
+                 dir_fd=None if os.supports_fd else dir_fd, **kwargs)
+
+class Environment(object):
+    """
+    Implements the Environment API.
+    """
+    def __init__(self, db):
+        self.db = db
+
+    def txn_begin(self):
+        return DBAPITxn("DBAPIDb Transaction", self.db)
+
+class Table(object):
+    """
+    Implements Table interface.
+    """
+    def __init__(self, funcs):
+        self.funcs = funcs
+
+    def cursor(self):
+        """
+        Returns a Cursor for this Table.
+        """
+        return self.funcs["cursor_func"]()
+
+    def put(self, key, data, txn=None):
+        self.funcs["add_func"](data, txn)
+
+class Map(object):
+    """
+    Implements the map API for person_map, etc.
+    
+    Takes a Table() as argument.
+    """
+    def __init__(self, table, 
+                 keys_func="handles_func", 
+                 contains_func="has_handle_func",
+                 *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.table = table
+        self.keys_func = keys_func
+        self.contains_func = contains_func
+
+    def keys(self):
+        return self.table.funcs[self.keys_func]()
+
+    def values(self):
+        return self.table.funcs["cursor_func"]()
+
+    def __contains__(self, key):
+        return self.table.funcs[self.contains_func](key)
+
+    def __getitem__(self, key):
+        if self.table.funcs[self.contains_func](key):
+            return self.table.funcs["raw_func"](key)
+
+    def __len__(self):
+        return self.table.funcs["count_func"]()
+
+class MetaCursor(object):
+    def __init__(self):
+        pass
+    def __enter__(self):
+        return self
+    def __iter__(self):
+        return self.__next__()
+    def __next__(self):
+        yield None
+    def __exit__(self, *args, **kwargs):
+        pass
+    def iter(self):
+        yield None
+    def first(self):
+        self._iter = self.__iter__()
+        return self.next()
+    def next(self):
+        try:
+            return next(self._iter)
+        except:
+            return None
+    def close(self):
+        pass
+
+class Cursor(object):
+    def __init__(self, map):
+        self.map = map
+        self._iter = self.__iter__()
+    def __enter__(self):
+        return self
+    def __iter__(self):
+        for item in self.map.keys():
+            yield (bytes(item, "utf-8"), self.map[item])
+    def __next__(self):
+        try:
+            return self._iter.__next__()
+        except StopIteration:
+            return None
+    def __exit__(self, *args, **kwargs):
+        pass
+    def iter(self):
+        for item in self.map.keys():
+            yield (bytes(item, "utf-8"), self.map[item])
+    def first(self):
+        self._iter = self.__iter__()
+        try:
+            return next(self._iter)
+        except:
+            return
+    def next(self):
+        try:
+            return next(self._iter)
+        except:
+            return
+    def close(self):
+        pass
+
+class Bookmarks(object):
+    def __init__(self):
+        self.handles = []
+    def get(self):
+        return self.handles
+    def append(self, handle):
+        self.handles.append(handle)
+
+class DBAPITxn(DbTxn):
+    def __init__(self, message, db, batch=False):
+        DbTxn.__init__(self, message, db, batch)
+
+    def get(self, key, default=None, txn=None, **kwargs):
+        """
+        Returns the data object associated with key
+        """
+        if txn and key in txn:
+            return txn[key]
+        else:
+            return None
+
+    def put(self, handle, new_data, txn):
+        """
+        """
+        txn[handle] = new_data
+
+class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
+    """
+    A Gramps Database Backend. This replicates the grampsdb functions.
+    """
+    __signals__ = dict((obj+'-'+op, signal)
+                       for obj in
+                       ['person', 'family', 'event', 'place',
+                        'source', 'citation', 'media', 'note', 'repository', 'tag']
+                       for op, signal in zip(
+                               ['add',   'update', 'delete', 'rebuild'],
+                               [(list,), (list,),  (list,),   None]
+                       )
+                   )
+    
+    # 2. Signals for long operations
+    __signals__.update(('long-op-'+op, signal) for op, signal in zip(
+        ['start',  'heartbeat', 'end'],
+        [(object,), None,       None]
+        ))
+
+    # 3. Special signal for change in home person
+    __signals__['home-person-changed'] = None
+
+    # 4. Signal for change in person group name, parameters are
+    __signals__['person-groupname-rebuild'] = (str, str)
+
+    __callback_map = {}
+
+    def __init__(self, directory=None):
+        DbReadBase.__init__(self)
+        DbWriteBase.__init__(self)
+        Callback.__init__(self)
+        self._tables['Person'].update(
+            {
+                "handle_func": self.get_person_from_handle, 
+                "gramps_id_func": self.get_person_from_gramps_id,
+                "class_func": Person,
+                "cursor_func": self.get_person_cursor,
+                "handles_func": self.get_person_handles,
+                "add_func": self.add_person,
+                "commit_func": self.commit_person,
+                "iter_func": self.iter_people,
+                "ids_func": self.get_person_gramps_ids,
+                "has_handle_func": self.has_handle_for_person,
+                "has_gramps_id_func": self.has_gramps_id_for_person,
+                "count": self.get_number_of_people,
+                "raw_func": self._get_raw_person_data,
+            })
+        self._tables['Family'].update(
+            {
+                "handle_func": self.get_family_from_handle, 
+                "gramps_id_func": self.get_family_from_gramps_id,
+                "class_func": Family,
+                "cursor_func": self.get_family_cursor,
+                "handles_func": self.get_family_handles,
+                "add_func": self.add_family,
+                "commit_func": self.commit_family,
+                "iter_func": self.iter_families,
+                "ids_func": self.get_family_gramps_ids,
+                "has_handle_func": self.has_handle_for_family,
+                "has_gramps_id_func": self.has_gramps_id_for_family,
+                "count": self.get_number_of_families,
+                "raw_func": self._get_raw_family_data,
+            })
+        self._tables['Source'].update(
+            {
+                "handle_func": self.get_source_from_handle, 
+                "gramps_id_func": self.get_source_from_gramps_id,
+                "class_func": Source,
+                "cursor_func": self.get_source_cursor,
+                "handles_func": self.get_source_handles,
+                "add_func": self.add_source,
+                "commit_func": self.commit_source,
+                "iter_func": self.iter_sources,
+                "ids_func": self.get_source_gramps_ids,
+                "has_handle_func": self.has_handle_for_source,
+                "has_gramps_id_func": self.has_gramps_id_for_source,
+                "count": self.get_number_of_sources,
+                "raw_func": self._get_raw_source_data,
+                })
+        self._tables['Citation'].update(
+            {
+                "handle_func": self.get_citation_from_handle, 
+                "gramps_id_func": self.get_citation_from_gramps_id,
+                "class_func": Citation,
+                "cursor_func": self.get_citation_cursor,
+                "handles_func": self.get_citation_handles,
+                "add_func": self.add_citation,
+                "commit_func": self.commit_citation,
+                "iter_func": self.iter_citations,
+                "ids_func": self.get_citation_gramps_ids,
+                "has_handle_func": self.has_handle_for_citation,
+                "has_gramps_id_func": self.has_gramps_id_for_citation,
+                "count": self.get_number_of_citations,
+                "raw_func": self._get_raw_citation_data,
+            })
+        self._tables['Event'].update(
+            {
+                "handle_func": self.get_event_from_handle, 
+                "gramps_id_func": self.get_event_from_gramps_id,
+                "class_func": Event,
+                "cursor_func": self.get_event_cursor,
+                "handles_func": self.get_event_handles,
+                "add_func": self.add_event,
+                "commit_func": self.commit_event,
+                "iter_func": self.iter_events,
+                "ids_func": self.get_event_gramps_ids,
+                "has_handle_func": self.has_handle_for_event,
+                "has_gramps_id_func": self.has_gramps_id_for_event,
+                "count": self.get_number_of_events,
+                "raw_func": self._get_raw_event_data,
+            })
+        self._tables['Media'].update(
+            {
+                "handle_func": self.get_object_from_handle, 
+                "gramps_id_func": self.get_object_from_gramps_id,
+                "class_func": MediaObject,
+                "cursor_func": self.get_media_cursor,
+                "handles_func": self.get_media_object_handles,
+                "add_func": self.add_object,
+                "commit_func": self.commit_media_object,
+                "iter_func": self.iter_media_objects,
+                "ids_func": self.get_media_gramps_ids,
+                "has_handle_func": self.has_handle_for_media,
+                "has_gramps_id_func": self.has_gramps_id_for_media,
+                "count": self.get_number_of_media_objects,
+                "raw_func": self._get_raw_media_data,
+            })
+        self._tables['Place'].update(
+            {
+                "handle_func": self.get_place_from_handle, 
+                "gramps_id_func": self.get_place_from_gramps_id,
+                "class_func": Place,
+                "cursor_func": self.get_place_cursor,
+                "handles_func": self.get_place_handles,
+                "add_func": self.add_place,
+                "commit_func": self.commit_place,
+                "iter_func": self.iter_places,
+                "ids_func": self.get_place_gramps_ids,
+                "has_handle_func": self.has_handle_for_place,
+                "has_gramps_id_func": self.has_gramps_id_for_place,
+                "count": self.get_number_of_places,
+                "raw_func": self._get_raw_place_data,
+            })
+        self._tables['Repository'].update(
+            {
+                "handle_func": self.get_repository_from_handle, 
+                "gramps_id_func": self.get_repository_from_gramps_id,
+                "class_func": Repository,
+                "cursor_func": self.get_repository_cursor,
+                "handles_func": self.get_repository_handles,
+                "add_func": self.add_repository,
+                "commit_func": self.commit_repository,
+                "iter_func": self.iter_repositories,
+                "ids_func": self.get_repository_gramps_ids,
+                "has_handle_func": self.has_handle_for_repository,
+                "has_gramps_id_func": self.has_gramps_id_for_repository,
+                "count": self.get_number_of_repositories,
+                "raw_func": self._get_raw_repository_data,
+            })
+        self._tables['Note'].update(
+            {
+                "handle_func": self.get_note_from_handle, 
+                "gramps_id_func": self.get_note_from_gramps_id,
+                "class_func": Note,
+                "cursor_func": self.get_note_cursor,
+                "handles_func": self.get_note_handles,
+                "add_func": self.add_note,
+                "commit_func": self.commit_note,
+                "iter_func": self.iter_notes,
+                "ids_func": self.get_note_gramps_ids,
+                "has_handle_func": self.has_handle_for_note,
+                "has_gramps_id_func": self.has_gramps_id_for_note,
+                "count": self.get_number_of_notes,
+                "raw_func": self._get_raw_note_data,
+            })
+        self._tables['Tag'].update(
+            {
+                "handle_func": self.get_tag_from_handle, 
+                "gramps_id_func": None,
+                "class_func": Tag,
+                "cursor_func": self.get_tag_cursor,
+                "handles_func": self.get_tag_handles,
+                "add_func": self.add_tag,
+                "commit_func": self.commit_tag,
+                "iter_func": self.iter_tags,
+                "count": self.get_number_of_tags,
+            })
+        # skip GEDCOM cross-ref check for now:
+        self.set_feature("skip-check-xref", True)
+        self.set_feature("skip-import-additions", True)
+        self.readonly = False
+        self.db_is_open = True
+        self.name_formats = []
+        self.bookmarks = Bookmarks()
+        self.family_bookmarks = Bookmarks()
+        self.event_bookmarks = Bookmarks()
+        self.place_bookmarks = Bookmarks()
+        self.citation_bookmarks = Bookmarks()
+        self.source_bookmarks = Bookmarks()
+        self.repo_bookmarks = Bookmarks()
+        self.media_bookmarks = Bookmarks()
+        self.note_bookmarks = Bookmarks()
+        self.set_person_id_prefix('I%04d')
+        self.set_object_id_prefix('O%04d')
+        self.set_family_id_prefix('F%04d')
+        self.set_citation_id_prefix('C%04d')
+        self.set_source_id_prefix('S%04d')
+        self.set_place_id_prefix('P%04d')
+        self.set_event_id_prefix('E%04d')
+        self.set_repository_id_prefix('R%04d')
+        self.set_note_id_prefix('N%04d')
+        # ----------------------------------
+        self.id_trans  = DBAPITxn("ID Transaction", self)
+        self.fid_trans = DBAPITxn("FID Transaction", self)
+        self.pid_trans = DBAPITxn("PID Transaction", self)
+        self.cid_trans = DBAPITxn("CID Transaction", self)
+        self.sid_trans = DBAPITxn("SID Transaction", self)
+        self.oid_trans = DBAPITxn("OID Transaction", self)
+        self.rid_trans = DBAPITxn("RID Transaction", self)
+        self.nid_trans = DBAPITxn("NID Transaction", self)
+        self.eid_trans = DBAPITxn("EID Transaction", self)
+        self.cmap_index = 0
+        self.smap_index = 0
+        self.emap_index = 0
+        self.pmap_index = 0
+        self.fmap_index = 0
+        self.lmap_index = 0
+        self.omap_index = 0
+        self.rmap_index = 0
+        self.nmap_index = 0
+        self.env = Environment(self)
+        self.person_map = Map(Table(self._tables["Person"]))
+        self.person_id_map = Map(Table(self._tables["Person"]),
+                                 keys_func="ids_func",
+                                 contains_func="has_gramps_id_func")
+        self.family_map = Map(Table(self._tables["Family"]))
+        self.family_id_map = Map(Table(self._tables["Family"]),
+                                 keys_func="ids_func",
+                                 contains_func="has_gramps_id_func")
+        self.place_map  = Map(Table(self._tables["Place"]))
+        self.place_id_map = Map(Table(self._tables["Place"]),
+                                 keys_func="ids_func",
+                                 contains_func="has_gramps_id_func")
+        self.citation_map = Map(Table(self._tables["Citation"]))
+        self.citation_id_map = Map(Table(self._tables["Citation"]),
+                                 keys_func="ids_func",
+                                 contains_func="has_gramps_id_func")
+        self.source_map = Map(Table(self._tables["Source"]))
+        self.source_id_map = Map(Table(self._tables["Source"]),
+                                 keys_func="ids_func",
+                                 contains_func="has_gramps_id_func")
+        self.repository_map  = Map(Table(self._tables["Repository"]))
+        self.repository_id_map = Map(Table(self._tables["Repository"]),
+                                 keys_func="ids_func",
+                                 contains_func="has_gramps_id_func")
+        self.note_map = Map(Table(self._tables["Note"]))
+        self.note_id_map = Map(Table(self._tables["Note"]),
+                                 keys_func="ids_func",
+                                 contains_func="has_gramps_id_func")
+        self.media_map  = Map(Table(self._tables["Media"]))
+        self.media_id_map = Map(Table(self._tables["Media"]),
+                                 keys_func="ids_func",
+                                 contains_func="has_gramps_id_func")
+        self.event_map  = Map(Table(self._tables["Event"]))
+        self.event_id_map = Map(Table(self._tables["Event"]), 
+                                 keys_func="ids_func",
+                                 contains_func="has_gramps_id_func")
+        self.tag_map  = Map(Table(self._tables["Tag"]))
+        self.metadata   = Map(Table({"cursor_func": lambda: MetaCursor()}))
+        self.name_group = {}
+        self.undo_callback = None
+        self.redo_callback = None
+        self.undo_history_callback = None
+        self.modified   = 0
+        self.txn = DBAPITxn("DBAPI Transaction", self)
+        self.transaction = None
+        self.undodb = DbUndo(self)
+        self.abort_possible = False
+        self._bm_changes = 0
+        self._directory = directory
+        self.full_name = None
+        self.path = None
+        self.brief_name = None
+        self.genderStats = GenderStats() # can pass in loaded stats as dict
+        self.owner = Researcher()
+        if directory:
+            self.load(directory)
+
+    def version_supported(self):
+        """Return True when the file has a supported version."""
+        return True
+
+    def get_table_names(self):
+        """Return a list of valid table names."""
+        return list(self._tables.keys())
+
+    def get_table_metadata(self, table_name):
+        """Return the metadata for a valid table name."""
+        if table_name in self._tables:
+            return self._tables[table_name]
+        return None
+
+    def transaction_commit(self, txn):
+        ## FIXME
+        pass
+
+    def get_undodb(self):
+        ## FIXME
+        return None
+
+    def transaction_abort(self, txn):
+        ## FIXME
+        pass
+
+    @staticmethod
+    def _validated_id_prefix(val, default):
+        if isinstance(val, str) and val:
+            try:
+                str_ = val % 1
+            except TypeError:           # missing conversion specifier
+                prefix_var = val + "%d"
+            except ValueError:          # incomplete format
+                prefix_var = default+"%04d"
+            else:
+                prefix_var = val        # OK as given
+        else:
+            prefix_var = default+"%04d" # not a string or empty string
+        return prefix_var
+
+    @staticmethod
+    def __id2user_format(id_pattern):
+        """
+        Return a method that accepts a Gramps ID and adjusts it to the users
+        format.
+        """
+        pattern_match = re.match(r"(.*)%[0 ](\d+)[diu]$", id_pattern)
+        if pattern_match:
+            str_prefix = pattern_match.group(1)
+            nr_width = int(pattern_match.group(2))
+            def closure_func(gramps_id):
+                if gramps_id and gramps_id.startswith(str_prefix):
+                    id_number = gramps_id[len(str_prefix):]
+                    if id_number.isdigit():
+                        id_value = int(id_number, 10)
+                        #if len(str(id_value)) > nr_width:
+                        #    # The ID to be imported is too large to fit in the
+                        #    # users format. For now just create a new ID,
+                        #    # because that is also what happens with IDs that
+                        #    # are identical to IDs already in the database. If
+                        #    # the problem of colliding import and already
+                        #    # present IDs is solved the code here also needs
+                        #    # some solution.
+                        #    gramps_id = id_pattern % 1
+                        #else:
+                        gramps_id = id_pattern % id_value
+                return gramps_id
+        else:
+            def closure_func(gramps_id):
+                return gramps_id
+        return closure_func
+
+    def set_person_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Person ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as I%d or I%04d.
+        """
+        self.person_prefix = self._validated_id_prefix(val, "I")
+        self.id2user_format = self.__id2user_format(self.person_prefix)
+
+    def set_citation_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Citation ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as C%d or C%04d.
+        """
+        self.citation_prefix = self._validated_id_prefix(val, "C")
+        self.cid2user_format = self.__id2user_format(self.citation_prefix)
+            
+    def set_source_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Source ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as S%d or S%04d.
+        """
+        self.source_prefix = self._validated_id_prefix(val, "S")
+        self.sid2user_format = self.__id2user_format(self.source_prefix)
+            
+    def set_object_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS MediaObject ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as O%d or O%04d.
+        """
+        self.mediaobject_prefix = self._validated_id_prefix(val, "O")
+        self.oid2user_format = self.__id2user_format(self.mediaobject_prefix)
+
+    def set_place_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Place ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as P%d or P%04d.
+        """
+        self.place_prefix = self._validated_id_prefix(val, "P")
+        self.pid2user_format = self.__id2user_format(self.place_prefix)
+
+    def set_family_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Family ID values. The string is
+        expected to be in the form of a simple text string, or in a format
+        that contains a C/Python style format string using %d, such as F%d
+        or F%04d.
+        """
+        self.family_prefix = self._validated_id_prefix(val, "F")
+        self.fid2user_format = self.__id2user_format(self.family_prefix)
+
+    def set_event_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Event ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as E%d or E%04d.
+        """
+        self.event_prefix = self._validated_id_prefix(val, "E")
+        self.eid2user_format = self.__id2user_format(self.event_prefix)
+
+    def set_repository_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Repository ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as R%d or R%04d.
+        """
+        self.repository_prefix = self._validated_id_prefix(val, "R")
+        self.rid2user_format = self.__id2user_format(self.repository_prefix)
+
+    def set_note_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Note ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as N%d or N%04d.
+        """
+        self.note_prefix = self._validated_id_prefix(val, "N")
+        self.nid2user_format = self.__id2user_format(self.note_prefix)
+
+    def __find_next_gramps_id(self, prefix, map_index, trans):
+        """
+        Helper function for find_next_<object>_gramps_id methods
+        """
+        index = prefix % map_index
+        while trans.get(str(index), txn=self.txn) is not None:
+            map_index += 1
+            index = prefix % map_index
+        map_index += 1
+        return (map_index, index)
+        
+    def find_next_person_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Person object based off the 
+        person ID prefix.
+        """
+        self.pmap_index, gid = self.__find_next_gramps_id(self.person_prefix,
+                                          self.pmap_index, self.id_trans)
+        return gid
+
+    def find_next_place_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Place object based off the 
+        place ID prefix.
+        """
+        self.lmap_index, gid = self.__find_next_gramps_id(self.place_prefix,
+                                          self.lmap_index, self.pid_trans)
+        return gid
+
+    def find_next_event_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Event object based off the 
+        event ID prefix.
+        """
+        self.emap_index, gid = self.__find_next_gramps_id(self.event_prefix,
+                                          self.emap_index, self.eid_trans)
+        return gid
+
+    def find_next_object_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a MediaObject object based
+        off the media object ID prefix.
+        """
+        self.omap_index, gid = self.__find_next_gramps_id(self.mediaobject_prefix,
+                                          self.omap_index, self.oid_trans)
+        return gid
+
+    def find_next_citation_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Citation object based off the 
+        citation ID prefix.
+        """
+        self.cmap_index, gid = self.__find_next_gramps_id(self.citation_prefix,
+                                          self.cmap_index, self.cid_trans)
+        return gid
+
+    def find_next_source_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Source object based off the 
+        source ID prefix.
+        """
+        self.smap_index, gid = self.__find_next_gramps_id(self.source_prefix,
+                                          self.smap_index, self.sid_trans)
+        return gid
+
+    def find_next_family_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Family object based off the 
+        family ID prefix.
+        """
+        self.fmap_index, gid = self.__find_next_gramps_id(self.family_prefix,
+                                          self.fmap_index, self.fid_trans)
+        return gid
+
+    def find_next_repository_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Respository object based 
+        off the repository ID prefix.
+        """
+        self.rmap_index, gid = self.__find_next_gramps_id(self.repository_prefix,
+                                          self.rmap_index, self.rid_trans)
+        return gid
+
+    def find_next_note_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Note object based off the 
+        note ID prefix.
+        """
+        self.nmap_index, gid = self.__find_next_gramps_id(self.note_prefix,
+                                          self.nmap_index, self.nid_trans)
+        return gid
+
+    def get_mediapath(self):
+        return None
+
+    def get_name_group_keys(self):
+        return []
+
+    def get_name_group_mapping(self, key):
+        return None
+
+    def get_person_handles(self, sort_handles=False):
+        ## Fixme: implement sort
+        cur = self.dbapi.execute("select handle from person;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_family_handles(self, sort_handles=False):
+        ## Fixme: implement sort
+        cur = self.dbapi.execute("select handle from family;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_event_handles(self, sort_handles=False):
+        ## Fixme: implement sort
+        cur = self.dbapi.execute("select handle from event;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_citation_handles(self, sort_handles=False):
+        ## Fixme: implement sort
+        cur = self.dbapi.execute("select handle from citation;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_source_handles(self, sort_handles=False):
+        ## Fixme: implement sort
+        cur = self.dbapi.execute("select handle from source;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_place_handles(self, sort_handles=False):
+        ## Fixme: implement sort
+        cur = self.dbapi.execute("select handle from place;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_repository_handles(self, sort_handles=False):
+        ## Fixme: implement sort
+        cur = self.dbapi.execute("select handle from repository;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_media_object_handles(self, sort_handles=False):
+        ## Fixme: implement sort
+        cur = self.dbapi.execute("select handle from media;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_note_handles(self, sort_handles=False):
+        ## Fixme: implement sort
+        cur = self.dbapi.execute("select handle from note;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_tag_handles(self, sort_handles=False):
+        # FIXME: implement sort
+        cur = self.dbapi.execute("select handle from tag;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_event_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
+        event = None
+        if handle in self.event_map:
+            event = Event.create(self._get_raw_event_data(handle))
+        return event
+
+    def get_family_from_handle(self, handle): 
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
+        family = None
+        if handle in self.family_map:
+            family = Family.create(self._get_raw_family_data(handle))
+        return family
+
+    def get_repository_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
+        repository = None
+        if handle in self.repository_map:
+            repository = Repository.create(self._get_raw_repository_data(handle))
+        return repository
+
+    def get_person_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
+        person = None
+        if handle in self.person_map:
+            person = Person.create(self._get_raw_person_data(handle))
+        return person
+
+    def get_place_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
+        place = None
+        if handle in self.place_map:
+            place = Place.create(self._get_raw_place_data(handle))
+        return place
+
+    def get_citation_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
+        citation = None
+        if handle in self.citation_map:
+            citation = Citation.create(self._get_raw_citation_data(handle))
+        return citation
+
+    def get_source_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
+        source = None
+        if handle in self.source_map:
+            source = Source.create(self._get_raw_source_data(handle))
+        return source
+
+    def get_note_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
+        note = None
+        if handle in self.note_map:
+            note = Note.create(self._get_raw_note_data(handle))
+        return note
+
+    def get_object_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
+        media = None
+        if handle in self.media_map:
+            media = MediaObject.create(self._get_raw_media_data(handle))
+        return media
+
+    def get_tag_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
+        tag = None
+        if handle in self.tag_map:
+            tag = Tag.create(self._get_raw_tag_data(handle))
+        return tag
+
+    def get_default_person(self):
+        handle = self.get_default_handle()
+        if handle:
+            return self.get_person_from_handle(handle)
+        else:
+            return None
+
+    def iter_people(self):
+        return (Person.create(data[1]) for data in self.get_person_cursor())
+
+    def iter_person_handles(self):
+        return (data[0] for data in self.get_person_cursor())
+
+    def iter_families(self):
+        return (Family.create(data[1]) for data in self.get_family_cursor())
+
+    def iter_family_handles(self):
+        return (handle for handle in self.family_map.keys())
+
+    def get_tag_from_name(self, name):
+        ## Slow, but typically not too many tags:
+        for data in self.tag_map.values():
+            tag = Tag.create(data)
+            if tag.name == name:
+                return tag
+        return None
+
+    def get_person_from_gramps_id(self, gramps_id):
+        if gramps_id in self.person_id_map:
+            return Person.create(self.person_id_map[gramps_id])
+        return None
+
+    def get_family_from_gramps_id(self, gramps_id):
+        if gramps_id in self.family_id_map:
+            return Family.create(self.family_id_map[gramps_id])
+        return None
+
+    def get_citation_from_gramps_id(self, gramps_id):
+        if gramps_id in self.citation_id_map:
+            return Citation.create(self.citation_id_map[gramps_id])
+        return None
+
+    def get_source_from_gramps_id(self, gramps_id):
+        if gramps_id in self.source_id_map:
+            return Source.create(self.source_id_map[gramps_id])
+        return None
+
+    def get_event_from_gramps_id(self, gramps_id):
+        if gramps_id in self.event_id_map:
+            return Event.create(self.event_id_map[gramps_id])
+        return None
+
+    def get_media_from_gramps_id(self, gramps_id):
+        if gramps_id in self.media_id_map:
+            return MediaObject.create(self.media_id_map[gramps_id])
+        return None
+
+    def get_place_from_gramps_id(self, gramps_id):
+        if gramps_id in self.place_id_map:
+            return Place.create(self.place_id_map[gramps_id])
+        return None
+
+    def get_repository_from_gramps_id(self, gramps_id):
+        if gramps_id in self.repository_id_map:
+            return Repository.create(self.repository_id_map[gramps_id])
+        return None
+
+    def get_note_from_gramps_id(self, gramps_id):
+        if gramps_id in self.note_id_map:
+            return Note.create(self.note_id_map[gramps_id])
+        return None
+
+    def get_number_of_people(self):
+        cur = self.dbapi.execute("select count(handle) from person;")
+        row = cur.fetchone()
+        return row[0]
+
+    def get_number_of_events(self):
+        cur = self.dbapi.execute("select count(handle) from event;")
+        row = cur.fetchone()
+        return row[0]
+
+    def get_number_of_places(self):
+        cur = self.dbapi.execute("select count(handle) from place;")
+        row = cur.fetchone()
+        return row[0]
+
+    def get_number_of_tags(self):
+        cur = self.dbapi.execute("select count(handle) from tag;")
+        row = cur.fetchone()
+        return row[0]
+
+    def get_number_of_families(self):
+        cur = self.dbapi.execute("select count(handle) from family;")
+        row = cur.fetchone()
+        return row[0]
+
+    def get_number_of_notes(self):
+        cur = self.dbapi.execute("select count(handle) from note;")
+        row = cur.fetchone()
+        return row[0]
+
+    def get_number_of_citations(self):
+        cur = self.dbapi.execute("select count(handle) from citation;")
+        row = cur.fetchone()
+        return row[0]
+
+    def get_number_of_sources(self):
+        cur = self.dbapi.execute("select count(handle) from source;")
+        row = cur.fetchone()
+        return row[0]
+
+    def get_number_of_media_objects(self):
+        cur = self.dbapi.execute("select count(handle) from media;")
+        row = cur.fetchone()
+        return row[0]
+
+    def get_number_of_repositories(self):
+        cur = self.dbapi.execute("select count(handle) from repository;")
+        row = cur.fetchone()
+        return row[0]
+
+    def get_place_cursor(self):
+        return Cursor(self.place_map)
+
+    def get_person_cursor(self):
+        return Cursor(self.person_map)
+
+    def get_family_cursor(self):
+        return Cursor(self.family_map)
+
+    def get_event_cursor(self):
+        return Cursor(self.event_map)
+
+    def get_note_cursor(self):
+        return Cursor(self.note_map)
+
+    def get_tag_cursor(self):
+        return Cursor(self.tag_map)
+
+    def get_repository_cursor(self):
+        return Cursor(self.repository_map)
+
+    def get_media_cursor(self):
+        return Cursor(self.media_map)
+
+    def get_citation_cursor(self):
+        return Cursor(self.citation_map)
+
+    def get_source_cursor(self):
+        return Cursor(self.source_map)
+
+    def has_gramps_id(self, obj_key, gramps_id):
+        key2table = {
+            PERSON_KEY:     self.person_id_map, 
+            FAMILY_KEY:     self.family_id_map, 
+            SOURCE_KEY:     self.source_id_map, 
+            CITATION_KEY:   self.citation_id_map, 
+            EVENT_KEY:      self.event_id_map, 
+            MEDIA_KEY:      self.media_id_map, 
+            PLACE_KEY:      self.place_id_map, 
+            REPOSITORY_KEY: self.repository_id_map, 
+            NOTE_KEY:       self.note_id_map, 
+            }
+        return gramps_id in key2table[obj_key]
+
+    def has_person_handle(self, handle):
+        return handle in self.person_map
+
+    def has_family_handle(self, handle):
+        return handle in self.family_map
+
+    def has_citation_handle(self, handle):
+        return handle in self.citation_map
+
+    def has_source_handle(self, handle):
+        return handle in self.source_map
+
+    def has_repository_handle(self, handle):
+        return handle in self.repository_map
+
+    def has_note_handle(self, handle):
+        return handle in self.note_map
+
+    def has_place_handle(self, handle):
+        return handle in self.place_map
+
+    def has_event_handle(self, handle):
+        return handle in self.event_map
+
+    def has_tag_handle(self, handle):
+        return handle in self.tag_map
+
+    def has_object_handle(self, handle):
+        return handle in self.media_map
+
+    def has_name_group_key(self, key):
+        # FIXME:
+        return False
+
+    def set_name_group_mapping(self, key, value):
+        # FIXME:
+        pass
+
+    def set_default_person_handle(self, handle):
+        ## FIXME
+        pass
+
+    def set_mediapath(self, mediapath):
+        ## FIXME
+        pass
+
+    def get_raw_person_data(self, handle):
+        if handle in self.person_map:
+            return self.person_map[handle]
+        return None
+
+    def get_raw_family_data(self, handle):
+        if handle in self.family_map:
+            return self.family_map[handle]
+        return None
+
+    def get_raw_citation_data(self, handle):
+        if handle in self.citation_map:
+            return self.citation_map[handle]
+        return None
+
+    def get_raw_source_data(self, handle):
+        if handle in self.source_map:
+            return self.source_map[handle]
+        return None
+
+    def get_raw_repository_data(self, handle):
+        if handle in self.repository_map:
+            return self.repository_map[handle]
+        return None
+
+    def get_raw_note_data(self, handle):
+        if handle in self.note_map:
+            return self.note_map[handle]
+        return None
+
+    def get_raw_place_data(self, handle):
+        if handle in self.place_map:
+            return self.place_map[handle]
+        return None
+
+    def get_raw_object_data(self, handle):
+        if handle in self.media_map:
+            return self.media_map[handle]
+        return None
+
+    def get_raw_tag_data(self, handle):
+        if handle in self.tag_map:
+            return self.tag_map[handle]
+        return None
+
+    def get_raw_event_data(self, handle):
+        if handle in self.event_map:
+            return self.event_map[handle]
+        return None
+
+    def add_person(self, person, trans, set_gid=True):
+        if not person.handle:
+            person.handle = create_id()
+        if not person.gramps_id and set_gid:
+            person.gramps_id = self.find_next_person_gramps_id()
+        self.commit_person(person, trans)
+        return person.handle
+
+    def add_family(self, family, trans, set_gid=True):
+        if not family.handle:
+            family.handle = create_id()
+        if not family.gramps_id and set_gid:
+            family.gramps_id = self.find_next_family_gramps_id()
+        self.commit_family(family, trans)
+        return family.handle
+
+    def add_citation(self, citation, trans, set_gid=True):
+        if not citation.handle:
+            citation.handle = create_id()
+        if not citation.gramps_id and set_gid:
+            citation.gramps_id = self.find_next_citation_gramps_id()
+        self.commit_citation(citation, trans)
+        return citation.handle
+
+    def add_source(self, source, trans, set_gid=True):
+        if not source.handle:
+            source.handle = create_id()
+        if not source.gramps_id and set_gid:
+            source.gramps_id = self.find_next_source_gramps_id()
+        self.commit_source(source, trans)
+        return source.handle
+
+    def add_repository(self, repository, trans, set_gid=True):
+        if not repository.handle:
+            repository.handle = create_id()
+        if not repository.gramps_id and set_gid:
+            repository.gramps_id = self.find_next_repository_gramps_id()
+        self.commit_repository(repository, trans)
+        return repository.handle
+
+    def add_note(self, note, trans, set_gid=True):
+        if not note.handle:
+            note.handle = create_id()
+        if not note.gramps_id and set_gid:
+            note.gramps_id = self.find_next_note_gramps_id()
+        self.commit_note(note, trans)
+        return note.handle
+
+    def add_place(self, place, trans, set_gid=True):
+        if not place.handle:
+            place.handle = create_id()
+        if not place.gramps_id and set_gid:
+            place.gramps_id = self.find_next_place_gramps_id()
+        self.commit_place(place, trans)
+        return place.handle
+
+    def add_event(self, event, trans, set_gid=True):
+        if not event.handle:
+            event.handle = create_id()
+        if not event.gramps_id and set_gid:
+            event.gramps_id = self.find_next_event_gramps_id()
+        self.commit_event(event, trans)
+        return event.handle
+
+    def add_tag(self, tag, trans):
+        if not tag.handle:
+            tag.handle = create_id()
+        self.commit_tag(tag, trans)
+        return tag.handle
+
+    def add_object(self, obj, transaction, set_gid=True):
+        """
+        Add a MediaObject to the database, assigning internal IDs if they have
+        not already been defined.
+        
+        If not set_gid, then gramps_id is not set.
+        """
+        if not obj.handle:
+            obj.handle = create_id()
+        if not obj.gramps_id and set_gid:
+            obj.gramps_id = self.find_next_object_gramps_id()
+        self.commit_media_object(obj, transaction)
+        return obj.handle
+
+    def commit_person(self, person, trans, change_time=None):
+        emit = None
+        if True or not trans.batch:
+            if person.handle in self.person_map:
+                emit = "person-update"
+                self.dbapi.execute("""UPDATE person SET gramps_id = ?, 
+                                                        blob = ? 
+                                                    WHERE handle = ?;""",
+                                   [person.gramps_id, 
+                                    pickle.dumps(person.serialize()),
+                                    person.handle])
+            else:
+                emit = "person-add"
+                self.dbapi.execute("""insert into person(handle, gramps_id, blob) 
+                                                  values(?, ?, ?);""", 
+                                   [person.handle, person.gramps_id, pickle.dumps(person.serialize())])
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([person.handle],))
+
+    def commit_family(self, family, trans, change_time=None):
+        emit = None
+        if True or not trans.batch:
+            if family.handle in self.family_map:
+                emit = "family-update"
+                self.dbapi.execute("""UPDATE family SET gramps_id = ?, 
+                                                        blob = ? 
+                                                    WHERE handle = ?;""",
+                                   [family.gramps_id, 
+                                    pickle.dumps(family.serialize()),
+                                    family.handle])
+            else:
+                emit = "family-add"
+                self.dbapi.execute("insert into family values(?, ?, ?);", 
+                                   [family.handle, family.gramps_id, 
+                                    pickle.dumps(family.serialize())])
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([family.handle],))
+
+    def commit_citation(self, citation, trans, change_time=None):
+        emit = None
+        if True or not trans.batch:
+            if citation.handle in self.citation_map:
+                emit = "citation-update"
+                self.dbapi.execute("""UPDATE citation SET gramps_id = ?, 
+                                                        blob = ? 
+                                                    WHERE handle = ?;""",
+                                   [citation.gramps_id, 
+                                    pickle.dumps(citation.serialize()),
+                                    citation.handle])
+            else:
+                emit = "citation-add"
+                self.dbapi.execute("insert into citation values(?, ?, ?);", 
+                           [citation.handle, citation.gramps_id, pickle.dumps(citation.serialize())])
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([citation.handle],))
+
+    def commit_source(self, source, trans, change_time=None):
+        emit = None
+        if True or not trans.batch:
+            if source.handle in self.source_map:
+                emit = "source-update"
+                self.dbapi.execute("""UPDATE source SET gramps_id = ?, 
+                                                        blob = ? 
+                                                    WHERE handle = ?;""",
+                                   [source.gramps_id, 
+                                    pickle.dumps(source.serialize()),
+                                    source.handle])
+            else:
+                emit = "source-add"
+                self.dbapi.execute("insert into source values(?, ?, ?);", 
+                           [source.handle, source.gramps_id, pickle.dumps(source.serialize())])
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([source.handle],))
+
+    def commit_repository(self, repository, trans, change_time=None):
+        emit = None
+        if True or not trans.batch:
+            if repository.handle in self.repository_map:
+                emit = "repository-update"
+                self.dbapi.execute("""UPDATE repository SET gramps_id = ?, 
+                                                        blob = ? 
+                                                    WHERE handle = ?;""",
+                                   [repository.gramps_id, 
+                                    pickle.dumps(repository.serialize()),
+                                    repository.handle])
+            else:
+                emit = "repository-add"
+                self.dbapi.execute("insert into repository values(?, ?, ?);", 
+                           [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())])
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([repository.handle],))
+
+    def commit_note(self, note, trans, change_time=None):
+        emit = None
+        if True or not trans.batch:
+            if note.handle in self.note_map:
+                emit = "note-update"
+                self.dbapi.execute("""UPDATE note SET gramps_id = ?, 
+                                                        blob = ? 
+                                                    WHERE handle = ?;""",
+                                   [note.gramps_id, 
+                                    pickle.dumps(note.serialize()),
+                                    note.handle])
+            else:
+                emit = "note-add"
+                self.dbapi.execute("insert into note values(?, ?, ?);", 
+                           [note.handle, note.gramps_id, pickle.dumps(note.serialize())])
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([note.handle],))
+
+    def commit_place(self, place, trans, change_time=None):
+        emit = None
+        if True or not trans.batch:
+            if place.handle in self.place_map:
+                emit = "place-update"
+                self.dbapi.execute("""UPDATE place SET gramps_id = ?, 
+                                                        blob = ? 
+                                                    WHERE handle = ?;""",
+                                   [place.gramps_id, 
+                                    pickle.dumps(place.serialize()),
+                                    place.handle])
+            else:
+                emit = "place-add"
+                self.dbapi.execute("insert into place values(?, ?, ?);", 
+                           [place.handle, place.gramps_id, pickle.dumps(place.serialize())])
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([place.handle],))
+
+    def commit_event(self, event, trans, change_time=None):
+        emit = None
+        if True or not trans.batch:
+            if event.handle in self.event_map:
+                emit = "event-update"
+                self.dbapi.execute("""UPDATE event SET gramps_id = ?, 
+                                                        blob = ? 
+                                                    WHERE handle = ?;""",
+                                   [event.gramps_id, 
+                                    pickle.dumps(event.serialize()),
+                                    event.handle])
+            else:
+                emit = "event-add"
+                self.dbapi.execute("insert into event values(?, ?, ?);", 
+                           [event.handle, event.gramps_id, pickle.dumps(event.serialize())])
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([event.handle],))
+
+    def commit_tag(self, tag, trans, change_time=None):
+        emit = None
+        if True or not trans.batch:
+            if tag.handle in self.tag_map:
+                emit = "tag-update"
+                self.dbapi.execute("""UPDATE tag SET blob = ? 
+                                             WHERE handle = ?;""",
+                                   [pickle.dumps(tag.serialize()),
+                                    tag.handle])
+            else:
+                emit = "tag-add"
+                self.dbapi.execute("insert into tag values(?, ?);", 
+                           [tag.handle, pickle.dumps(tag.serialize())])
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([tag.handle],))
+
+    def commit_media_object(self, media, trans, change_time=None):
+        emit = None
+        if True or not trans.batch:
+            if media.handle in self.media_map:
+                emit = "media-update"
+                self.dbapi.execute("""UPDATE media SET gramps_id = ?, 
+                                                        blob = ? 
+                                                    WHERE handle = ?;""",
+                                   [media.gramps_id, 
+                                    pickle.dumps(media.serialize()),
+                                    media.handle])
+            else:
+                emit = "media-add"
+                self.dbapi.execute("insert into media values(?, ?, ?);", 
+                           [media.handle, media.gramps_id, pickle.dumps(media.serialize())])
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([media.handle],))
+
+    def get_gramps_ids(self, obj_key):
+        key2table = {
+            PERSON_KEY:     self.person_id_map, 
+            FAMILY_KEY:     self.family_id_map, 
+            CITATION_KEY:   self.citation_id_map, 
+            SOURCE_KEY:     self.source_id_map, 
+            EVENT_KEY:      self.event_id_map, 
+            MEDIA_KEY:      self.media_id_map, 
+            PLACE_KEY:      self.place_id_map, 
+            REPOSITORY_KEY: self.repository_id_map, 
+            NOTE_KEY:       self.note_id_map, 
+            }
+        return list(key2table[obj_key].keys())
+
+    def transaction_begin(self, transaction):
+        ## FIXME
+        return 
+
+    def set_researcher(self, owner):
+        self.owner.set_from(owner)
+
+    def get_researcher(self):
+        return self.owner
+
+    def request_rebuild(self):
+        self.emit('person-rebuild')
+        self.emit('family-rebuild')
+        self.emit('place-rebuild')
+        self.emit('source-rebuild')
+        self.emit('citation-rebuild')
+        self.emit('media-rebuild')
+        self.emit('event-rebuild')
+        self.emit('repository-rebuild')
+        self.emit('note-rebuild')
+        self.emit('tag-rebuild')
+
+    def copy_from_db(self, db):
+        """
+        A (possibily) implementation-specific method to get data from
+        db into this database.
+        """
+        for key in db._tables.keys():
+            cursor = db._tables[key]["cursor_func"]
+            class_ = db._tables[key]["class_func"]
+            for (handle, data) in cursor():
+                map = getattr(self, "%s_map" % key.lower())
+                map[handle] = class_.create(data)
+
+    def get_transaction_class(self):
+        """
+        Get the transaction class associated with this database backend.
+        """
+        return DBAPITxn
+
+    def get_from_name_and_handle(self, table_name, handle):
+        """
+        Returns a gen.lib object (or None) given table_name and
+        handle.
+
+        Examples:
+
+        >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008")
+        >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23")
+        """
+        if table_name in self._tables:
+            return self._tables[table_name]["handle_func"](handle)
+        return None
+
+    def get_from_name_and_gramps_id(self, table_name, gramps_id):
+        """
+        Returns a gen.lib object (or None) given table_name and
+        Gramps ID.
+
+        Examples:
+
+        >>> self.get_from_name_and_gramps_id("Person", "I00002")
+        >>> self.get_from_name_and_gramps_id("Family", "F056")
+        >>> self.get_from_name_and_gramps_id("Media", "M00012")
+        """
+        if table_name in self._tables:
+            return self._tables[table_name]["gramps_id_func"](gramps_id)
+        return None
+
+    def remove_person(self, handle, transaction):
+        """
+        Remove the Person specified by the database handle from the database, 
+        preserving the change in the passed transaction. 
+        """
+
+        if self.readonly or not handle:
+            return
+        if handle in self.person_map:
+            person = Person.create(self.person_map[handle])
+            #del self.person_map[handle]
+            #del self.person_id_map[person.gramps_id]
+            self.dbapi.execute("DELETE from person WHERE handle = ?;", [handle])
+            self.emit("person-delete", ([handle],))
+
+    def remove_source(self, handle, transaction):
+        """
+        Remove the Source specified by the database handle from the
+        database, preserving the change in the passed transaction. 
+        """
+        self.__do_remove(handle, transaction, self.source_map, 
+                         self.source_id_map, SOURCE_KEY)
+
+    def remove_citation(self, handle, transaction):
+        """
+        Remove the Citation specified by the database handle from the
+        database, preserving the change in the passed transaction. 
+        """
+        self.__do_remove(handle, transaction, self.citation_map, 
+                         self.citation_id_map, CITATION_KEY)
+
+    def remove_event(self, handle, transaction):
+        """
+        Remove the Event specified by the database handle from the
+        database, preserving the change in the passed transaction. 
+        """
+        self.__do_remove(handle, transaction, self.event_map, 
+                         self.event_id_map, EVENT_KEY)
+
+    def remove_object(self, handle, transaction):
+        """
+        Remove the MediaObjectPerson specified by the database handle from the
+        database, preserving the change in the passed transaction. 
+        """
+        self.__do_remove(handle, transaction, self.media_map, 
+                         self.media_id_map, MEDIA_KEY)
+
+    def remove_place(self, handle, transaction):
+        """
+        Remove the Place specified by the database handle from the
+        database, preserving the change in the passed transaction. 
+        """
+        self.__do_remove(handle, transaction, self.place_map, 
+                         self.place_id_map, PLACE_KEY)
+
+    def remove_family(self, handle, transaction):
+        """
+        Remove the Family specified by the database handle from the
+        database, preserving the change in the passed transaction. 
+        """
+        self.__do_remove(handle, transaction, self.family_map, 
+                         self.family_id_map, FAMILY_KEY)
+
+    def remove_repository(self, handle, transaction):
+        """
+        Remove the Repository specified by the database handle from the
+        database, preserving the change in the passed transaction. 
+        """
+        self.__do_remove(handle, transaction, self.repository_map, 
+                         self.repository_id_map, REPOSITORY_KEY)
+
+    def remove_note(self, handle, transaction):
+        """
+        Remove the Note specified by the database handle from the
+        database, preserving the change in the passed transaction. 
+        """
+        self.__do_remove(handle, transaction, self.note_map, 
+                         self.note_id_map, NOTE_KEY)
+
+    def remove_tag(self, handle, transaction):
+        """
+        Remove the Tag specified by the database handle from the
+        database, preserving the change in the passed transaction. 
+        """
+        self.__do_remove(handle, transaction, self.tag_map, 
+                         None, TAG_KEY)
+
+    def is_empty(self):
+        """
+        Return true if there are no [primary] records in the database
+        """
+        for table in self._tables:
+            if len(self._tables[table]["handles_func"]()) > 0:
+                return False
+        return True
+
+    def __do_remove(self, handle, transaction, data_map, data_id_map, key):
+        key2table = {
+            PERSON_KEY:     "person", 
+            FAMILY_KEY:     "family", 
+            SOURCE_KEY:     "source", 
+            CITATION_KEY:   "citation", 
+            EVENT_KEY:      "event", 
+            MEDIA_KEY:      "media", 
+            PLACE_KEY:      "place", 
+            REPOSITORY_KEY: "repository", 
+            NOTE_KEY:       "note", 
+            }
+        if self.readonly or not handle:
+            return
+        if handle in data_map:
+            self.dbapi.execute("DELETE from %s WHERE handle = ?;" % key2table[key], 
+                               [handle])
+            self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],))
+
+    def delete_primary_from_reference_map(self, handle, transaction, txn=None):
+        """
+        Remove all references to the primary object from the reference_map.
+        handle should be utf-8
+        """
+        primary_cur = self.get_reference_map_primary_cursor()
+
+        try:
+            ret = primary_cur.set(handle)
+        except:
+            ret = None
+        
+        remove_list = set()
+        while (ret is not None):
+            (key, data) = ret
+            
+            # data values are of the form:
+            #   ((primary_object_class_name, primary_object_handle),
+            #    (referenced_object_class_name, referenced_object_handle))
+            
+            # so we need the second tuple give us a reference that we can
+            # combine with the primary_handle to get the main key.
+            main_key = (handle.decode('utf-8'), pickle.loads(data)[1][1])
+            
+            # The trick is not to remove while inside the cursor,
+            # but collect them all and remove after the cursor is closed
+            remove_list.add(main_key)
+
+            ret = primary_cur.next_dup()
+
+        primary_cur.close()
+
+        # Now that the cursor is closed, we can remove things
+        for main_key in remove_list:
+            self.__remove_reference(main_key, transaction, txn)
+
+    def __remove_reference(self, key, transaction, txn):
+        """
+        Remove the reference specified by the key, preserving the change in 
+        the passed transaction.
+        """
+        if isinstance(key, tuple):
+            #create a byte string key, first validity check in python 3!
+            for val in key:
+                if isinstance(val, bytes):
+                    raise DbError(_('An attempt is made to save a reference key '
+                        'which is partly bytecode, this is not allowed.\n'
+                        'Key is %s') % str(key))
+            key = str(key)
+        if isinstance(key, str):
+            key = key.encode('utf-8')
+        if not self.readonly:
+            if not transaction.batch:
+                old_data = self.reference_map.get(key, txn=txn)
+                transaction.add(REFERENCE_KEY, TXNDEL, key, old_data, None)
+                #transaction.reference_del.append(str(key))
+            self.reference_map.delete(key, txn=txn)
+
+    ## Missing:
+
+    def backup(self):
+        ## FIXME
+        pass
+
+    def close(self):
+        if self._directory:
+            from gramps.plugins.export.exportxml import XmlWriter
+            from gramps.cli.user import User 
+            writer = XmlWriter(self, User(), strip_photos=0, compress=1)
+            filename = os.path.join(self._directory, "data.gramps")
+            writer.write(filename)
+            filename = os.path.join(self._directory, "meta_data.db")
+            touch(filename)
+
+    def find_backlink_handles(self, handle, include_classes=None):
+        ## FIXME
+        return []
+
+    def find_initial_person(self):
+        items = self.person_map.keys()
+        if len(items) > 0:
+            return self.get_person_from_handle(list(items)[0])
+        return None
+
+    def find_place_child_handles(self, handle):
+        ## FIXME
+        return []
+
+    def get_bookmarks(self):
+        return self.bookmarks
+
+    def get_child_reference_types(self):
+        ## FIXME
+        return []
+
+    def get_citation_bookmarks(self):
+        return self.citation_bookmarks
+
+    def get_cursor(self, table, txn=None, update=False, commit=False):
+        ## FIXME
+        ## called from a complete find_back_ref
+        pass
+
+    # cursors for lookups in the reference_map for back reference
+    # lookups. The reference_map has three indexes:
+    # the main index: a tuple of (primary_handle, referenced_handle)
+    # the primary_handle index: the primary_handle
+    # the referenced_handle index: the referenced_handle
+    # the main index is unique, the others allow duplicate entries.
+
+    def get_default_handle(self):
+        items = self.person_map.keys()
+        if len(items) > 0:
+            return list(items)[0]
+        return None
+
+    def get_event_attribute_types(self):
+        ## FIXME
+        return []
+
+    def get_event_bookmarks(self):
+        return self.event_bookmarks
+
+    def get_event_roles(self):
+        ## FIXME
+        return []
+
+    def get_event_types(self):
+        ## FIXME
+        return []
+
+    def get_family_attribute_types(self):
+        ## FIXME
+        return []
+
+    def get_family_bookmarks(self):
+        return self.family_bookmarks
+
+    def get_family_event_types(self):
+        ## FIXME
+        return []
+
+    def get_family_relation_types(self):
+        ## FIXME
+        return []
+
+    def get_media_attribute_types(self):
+        ## FIXME
+        return []
+
+    def get_media_bookmarks(self):
+        return self.media_bookmarks
+
+    def get_name_types(self):
+        ## FIXME
+        return []
+
+    def get_note_bookmarks(self):
+        return self.note_bookmarks
+
+    def get_note_types(self):
+        ## FIXME
+        return []
+
+    def get_origin_types(self):
+        ## FIXME
+        return []
+
+    def get_person_attribute_types(self):
+        ## FIXME
+        return []
+
+    def get_person_event_types(self):
+        ## FIXME
+        return []
+
+    def get_place_bookmarks(self):
+        return self.place_bookmarks
+
+    def get_place_tree_cursor(self):
+        ## FIXME
+        return []
+
+    def get_place_types(self):
+        ## FIXME
+        return []
+
+    def get_repo_bookmarks(self):
+        return self.repo_bookmarks
+
+    def get_repository_types(self):
+        ## FIXME
+        return []
+
+    def get_save_path(self):
+        return self._directory
+
+    def get_source_attribute_types(self):
+        ## FIXME
+        return []
+
+    def get_source_bookmarks(self):
+        return self.source_bookmarks
+
+    def get_source_media_types(self):
+        ## FIXME
+        return []
+
+    def get_surname_list(self):
+        ## FIXME
+        return []
+
+    def get_url_types(self):
+        ## FIXME
+        return []
+
+    def has_changed(self):
+        ## FIXME
+        return True
+
+    def is_open(self):
+        return self._directory is not None
+
+    def iter_citation_handles(self):
+        return (data[0] for data in self.get_citation_cursor())
+
+    def iter_citations(self):
+        return (Citation.create(data[1]) for data in self.get_citation_cursor())
+
+    def iter_event_handles(self):
+        return (data[0] for data in self.get_event_cursor())
+
+    def iter_events(self):
+        return (Event.create(data[1]) for data in self.get_event_cursor())
+
+    def iter_media_objects(self):
+        return (MediaObject.create(data[1]) for data in self.get_media_cursor())
+
+    def iter_note_handles(self):
+        return (data[0] for data in self.get_note_cursor())
+
+    def iter_notes(self):
+        return (Note.create(data[1]) for data in self.get_note_cursor())
+
+    def iter_place_handles(self):
+        return (data[0] for data in self.get_place_cursor())
+
+    def iter_places(self):
+        return (Place.create(data[1]) for data in self.get_place_cursor())
+
+    def iter_repositories(self):
+        return (Repository.create(data[1]) for data in self.get_repository_cursor())
+
+    def iter_repository_handles(self):
+        return (data[0] for data in self.get_repository_cursor())
+
+    def iter_source_handles(self):
+        return (data[0] for data in self.get_source_cursor())
+
+    def iter_sources(self):
+        return (Source.create(data[1]) for data in self.get_source_cursor())
+
+    def iter_tag_handles(self):
+        return (data[0] for data in self.get_tag_cursor())
+
+    def iter_tags(self):
+        return (Tag.create(data[1]) for data in self.get_tag_cursor())
+
+    def load(self, directory, pulse_progress=None, mode=None, 
+             force_schema_upgrade=False, 
+             force_bsddb_upgrade=False, 
+             force_bsddb_downgrade=False, 
+             force_python_upgrade=False):
+        # Run code from directory
+        import sqlite3
+        self.dbapi = sqlite3.connect(':memory:')
+        self.dbapi.row_factory = sqlite3.Row # allows access by name
+        # make sure schema is up to date:
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS person (
+                                    handle    TEXT PRIMARY KEY NOT NULL,
+                                    gramps_id TEXT             ,
+                                    blob      TEXT
+        );""")
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS family (
+                                    handle    TEXT PRIMARY KEY NOT NULL,
+                                    gramps_id TEXT             ,
+                                    blob      TEXT
+        );""")
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS source (
+                                    handle    TEXT PRIMARY KEY NOT NULL,
+                                    gramps_id TEXT             ,
+                                    blob      TEXT
+        );""")
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS citation (
+                                    handle    TEXT PRIMARY KEY NOT NULL,
+                                    gramps_id TEXT             ,
+                                    blob      TEXT
+        );""")
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS event (
+                                    handle    TEXT PRIMARY KEY NOT NULL,
+                                    gramps_id TEXT             ,
+                                    blob      TEXT
+        );""")
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS media (
+                                    handle    TEXT PRIMARY KEY NOT NULL,
+                                    gramps_id TEXT             ,
+                                    blob      TEXT
+        );""")
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS place (
+                                    handle    TEXT PRIMARY KEY NOT NULL,
+                                    gramps_id TEXT             ,
+                                    blob      TEXT
+        );""")
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS repository (
+                                    handle    TEXT PRIMARY KEY NOT NULL,
+                                    gramps_id TEXT             ,
+                                    blob      TEXT
+        );""")
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS note (
+                                    handle    TEXT PRIMARY KEY NOT NULL,
+                                    gramps_id TEXT             ,
+                                    blob      TEXT
+        );""")
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS tag (
+                                    handle    TEXT PRIMARY KEY NOT NULL,
+                                    blob      TEXT
+        );""")
+
+    def redo(self, update_history=True):
+        ## FIXME
+        pass
+
+    def restore(self):
+        ## FIXME
+        pass
+
+    def set_prefixes(self, person, media, family, source, citation, 
+                     place, event, repository, note):
+        ## FIXME
+        pass
+
+    def set_save_path(self, directory):
+        self._directory = directory
+        self.full_name = os.path.abspath(self._directory)
+        self.path = self.full_name
+        self.brief_name = os.path.basename(self._directory)
+
+    def undo(self, update_history=True):
+        ## FIXME
+        pass
+
+    def write_version(self, directory):
+        """Write files for a newly created DB."""
+        versionpath = os.path.join(directory, str(DBBACKEND))
+        _LOG.debug("Write database backend file to 'dbapi'")
+        with open(versionpath, "w") as version_file:
+            version_file.write("dbapi")
+
+    def report_bm_change(self):
+        """
+        Add 1 to the number of bookmark changes during this session.
+        """
+        self._bm_changes += 1
+
+    def db_has_bm_changes(self):
+        """
+        Return whethere there were bookmark changes during the session.
+        """
+        return self._bm_changes > 0
+
+    def get_summary(self):
+        """
+        Returns dictionary of summary item.
+        Should include, if possible:
+
+        _("Number of people")
+        _("Version")
+        _("Schema version")
+        """
+        return {
+            _("Number of people"): self.get_number_of_people(),
+        }
+
+    def get_dbname(self):
+        """
+        In DBAPI, the database is in a text file at the path
+        """
+        filepath = os.path.join(self._directory, "name.txt")
+        try:
+            name_file = open(filepath, "r")
+            name = name_file.readline().strip()
+            name_file.close()
+        except (OSError, IOError) as msg:
+            _LOG.error(str(msg))
+            name = None
+        return name
+
+    def reindex_reference_map(self):
+        ## FIXME
+        pass
+
+    def rebuild_secondary(self, update):
+        ## FIXME
+        pass
+
+    def prepare_import(self):
+        """
+        DBAPI does not commit data on gedcom import, but saves them
+        for later commit.
+        """
+        pass
+
+    def commit_import(self):
+        """
+        Commits the items that were queued up during the last gedcom
+        import for two step adding.
+        """
+        pass
+
+    def has_handle_for_person(self, key):
+        cur = self.dbapi.execute("select * from person where handle = ?", [key])
+        return cur.fetchone() != None
+
+    def has_handle_for_family(self, key):
+        cur = self.dbapi.execute("select * from family where handle = ?", [key])
+        return cur.fetchone() != None
+
+    def has_handle_for_source(self, key):
+        cur = self.dbapi.execute("select * from source where handle = ?", [key])
+        return cur.fetchone() != None
+
+    def has_handle_for_citation(self, key):
+        cur = self.dbapi.execute("select * from citation where handle = ?", [key])
+        return cur.fetchone() != None
+
+    def has_handle_for_event(self, key):
+        cur = self.dbapi.execute("select * from event where handle = ?", [key])
+        return cur.fetchone() != None
+
+    def has_handle_for_media(self, key):
+        cur = self.dbapi.execute("select * from media where handle = ?", [key])
+        return cur.fetchone() != None
+
+    def has_handle_for_place(self, key):
+        cur = self.dbapi.execute("select * from place where handle = ?", [key])
+        return cur.fetchone() != None
+
+    def has_handle_for_repository(self, key):
+        cur = self.dbapi.execute("select * from repository where handle = ?", [key])
+        return cur.fetchone() != None
+
+    def has_handle_for_note(self, key):
+        cur = self.dbapi.execute("select * from note where handle = ?", [key])
+        return cur.fetchone() != None
+
+    def has_gramps_id_for_person(self, key):
+        cur = self.dbapi.execute("select * from person where gramps_id = ?", [key])
+        return cur.fetchone() != None
+
+    def has_gramps_id_for_family(self, key):
+        cur = self.dbapi.execute("select * from family where gramps_id = ?", [key])
+        return cur.fetchone() != None
+
+    def has_gramps_id_for_source(self, key):
+        cur = self.dbapi.execute("select * from source where gramps_id = ?", [key])
+        return cur.fetchone() != None
+
+    def has_gramps_id_for_citation(self, key):
+        cur = self.dbapi.execute("select * from citation where gramps_id = ?", [key])
+        return cur.fetchone() != None
+
+    def has_gramps_id_for_event(self, key):
+        cur = self.dbapi.execute("select * from event where gramps_id = ?", [key])
+        return cur.fetchone() != None
+
+    def has_gramps_id_for_media(self, key):
+        cur = self.dbapi.execute("select * from media where gramps_id = ?", [key])
+        return cur.fetchone() != None
+
+    def has_gramps_id_for_place(self, key):
+        cur = self.dbapi.execute("select * from place where gramps_id = ?", [key])
+        return cur.fetchone() != None
+
+    def has_gramps_id_for_repository(self, key):
+        cur = self.dbapi.execute("select * from repository where gramps_id = ?", [key])
+        return cur.fetchone() != None
+
+    def has_gramps_id_for_note(self, key):
+        cur = self.dbapi.execute("select * from note where gramps_id = ?", [key])
+        return cur.fetchone() != None
+
+    def get_person_gramps_ids(self):
+        cur = self.dbapi.execute("select gramps_id from person;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_family_gramps_ids(self):
+        cur = self.dbapi.execute("select gramps_id from family;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_source_gramps_ids(self):
+        cur = self.dbapi.execute("select gramps_id from source;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_citation_gramps_ids(self):
+        cur = self.dbapi.execute("select gramps_id from citation;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_event_gramps_ids(self):
+        cur = self.dbapi.execute("select gramps_id from event;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_media_gramps_ids(self):
+        cur = self.dbapi.execute("select gramps_id from media;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_place_gramps_ids(self):
+        cur = self.dbapi.execute("select gramps from place;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_repository_gramps_ids(self):
+        cur = self.dbapi.execute("select gramps_id from repository;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_note_gramps_ids(self):
+        cur = self.dbapi.execute("select gramps_id from note;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def _get_raw_person_data(self, key):
+        cur = self.dbapi.execute("select blob from person where handle = ?", [key])
+        row = cur.fetchone()
+        if row:
+            return pickle.loads(row[0])
+
+    def _get_raw_family_data(self, key):
+        cur = self.dbapi.execute("select blob from family where handle = ?", [key])
+        row = cur.fetchone()
+        if row:
+            return pickle.loads(row[0])
+
+    def _get_raw_source_data(self, key):
+        cur = self.dbapi.execute("select blob from source where handle = ?", [key])
+        row = cur.fetchone()
+        if row:
+            return pickle.loads(row[0])
+
+    def _get_raw_citation_data(self, key):
+        cur = self.dbapi.execute("select blob from citation where handle = ?", [key])
+        row = cur.fetchone()
+        if row:
+            return pickle.loads(row[0])
+
+    def _get_raw_event_data(self, key):
+        cur = self.dbapi.execute("select blob from event where handle = ?", [key])
+        row = cur.fetchone()
+        if row:
+            return pickle.loads(row[0])
+
+    def _get_raw_media_data(self, key):
+        cur = self.dbapi.execute("select blob from media where handle = ?", [key])
+        row = cur.fetchone()
+        if row:
+            return pickle.loads(row[0])
+
+    def _get_raw_place_data(self, key):
+        cur = self.dbapi.execute("select blob from place where handle = ?", [key])
+        row = cur.fetchone()
+        if row:
+            return pickle.loads(row[0])
+
+    def _get_raw_repository_data(self, key):
+        cur = self.dbapi.execute("select blob from repository where handle = ?", [key])
+        row = cur.fetchone()
+        if row:
+            return pickle.loads(row[0])
+
+    def _get_raw_note_data(self, key):
+        cur = self.dbapi.execute("select blob from note where handle = ?", [key])
+        row = cur.fetchone()
+        if row:
+            return pickle.loads(row[0])
+
+    def _get_raw_tag_data(self, key):
+        cur = self.dbapi.execute("select blob from tag where handle = ?", [key])
+        row = cur.fetchone()
+        if row:
+            return pickle.loads(row[0])
+
diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 2eda2460d..8b8d8beb7 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -1690,7 +1690,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return (key for key in self.event_map.keys())
 
     def iter_events(self):
-        return (Events.create(key) for key in self.event_map.values())
+        return (Event.create(key) for key in self.event_map.values())
 
     def iter_media_objects(self):
         return (MediaObject.create(key) for key in self.media_map.values())

From 83c853726de9f31c9fb87177c0c91c0acacfc1c0 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 23 May 2015 02:15:44 -0400
Subject: [PATCH 037/105] DB-API 2.0 can now load/save from file; need to
 load/save metadata

---
 gramps/plugins/database/dbapi.py              | 31 +++++++++++++++++--
 .../defaults/default_settings.py              |  7 +++++
 2 files changed, 35 insertions(+), 3 deletions(-)
 create mode 100644 gramps/plugins/database/dbapi_support/defaults/default_settings.py

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index b5d61e5dc..c99146b6d 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -9,6 +9,7 @@ import time
 import re
 import os
 import logging
+import shutil
 
 #------------------------------------------------------------------------
 #
@@ -1261,6 +1262,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 self.dbapi.execute("""insert into person(handle, gramps_id, blob) 
                                                   values(?, ?, ?);""", 
                                    [person.handle, person.gramps_id, pickle.dumps(person.serialize())])
+            self.dbapi.commit()
         # Emit after added:
         if emit:
             self.emit(emit, ([person.handle],))
@@ -1281,6 +1283,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 self.dbapi.execute("insert into family values(?, ?, ?);", 
                                    [family.handle, family.gramps_id, 
                                     pickle.dumps(family.serialize())])
+            self.dbapi.commit()
         # Emit after added:
         if emit:
             self.emit(emit, ([family.handle],))
@@ -1300,6 +1303,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "citation-add"
                 self.dbapi.execute("insert into citation values(?, ?, ?);", 
                            [citation.handle, citation.gramps_id, pickle.dumps(citation.serialize())])
+            self.dbapi.commit()
         # Emit after added:
         if emit:
             self.emit(emit, ([citation.handle],))
@@ -1319,6 +1323,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "source-add"
                 self.dbapi.execute("insert into source values(?, ?, ?);", 
                            [source.handle, source.gramps_id, pickle.dumps(source.serialize())])
+            self.dbapi.commit()
         # Emit after added:
         if emit:
             self.emit(emit, ([source.handle],))
@@ -1338,6 +1343,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "repository-add"
                 self.dbapi.execute("insert into repository values(?, ?, ?);", 
                            [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())])
+            self.dbapi.commit()
         # Emit after added:
         if emit:
             self.emit(emit, ([repository.handle],))
@@ -1357,6 +1363,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "note-add"
                 self.dbapi.execute("insert into note values(?, ?, ?);", 
                            [note.handle, note.gramps_id, pickle.dumps(note.serialize())])
+            self.dbapi.commit()
         # Emit after added:
         if emit:
             self.emit(emit, ([note.handle],))
@@ -1376,6 +1383,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "place-add"
                 self.dbapi.execute("insert into place values(?, ?, ?);", 
                            [place.handle, place.gramps_id, pickle.dumps(place.serialize())])
+            self.dbapi.commit()
         # Emit after added:
         if emit:
             self.emit(emit, ([place.handle],))
@@ -1395,6 +1403,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "event-add"
                 self.dbapi.execute("insert into event values(?, ?, ?);", 
                            [event.handle, event.gramps_id, pickle.dumps(event.serialize())])
+            self.dbapi.commit()
         # Emit after added:
         if emit:
             self.emit(emit, ([event.handle],))
@@ -1412,6 +1421,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "tag-add"
                 self.dbapi.execute("insert into tag values(?, ?);", 
                            [tag.handle, pickle.dumps(tag.serialize())])
+            self.dbapi.commit()
         # Emit after added:
         if emit:
             self.emit(emit, ([tag.handle],))
@@ -1431,6 +1441,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "media-add"
                 self.dbapi.execute("insert into media values(?, ?, ?);", 
                            [media.handle, media.gramps_id, pickle.dumps(media.serialize())])
+            self.dbapi.commit()
         # Emit after added:
         if emit:
             self.emit(emit, ([media.handle],))
@@ -1706,6 +1717,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             writer.write(filename)
             filename = os.path.join(self._directory, "meta_data.db")
             touch(filename)
+            self.dbapi.close()
 
     def find_backlink_handles(self, handle, include_classes=None):
         ## FIXME
@@ -1907,9 +1919,15 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
              force_bsddb_downgrade=False, 
              force_python_upgrade=False):
         # Run code from directory
-        import sqlite3
-        self.dbapi = sqlite3.connect(':memory:')
-        self.dbapi.row_factory = sqlite3.Row # allows access by name
+        default_settings = {"__file__": 
+                            os.path.join(directory, "default_settings.py")}
+        settings_file = os.path.join(directory, "default_settings.py")
+        with open(settings_file) as f:
+            code = compile(f.read(), settings_file, 'exec')
+            exec(code, globals(), default_settings)
+
+        self.dbapi = default_settings["dbapi"]
+            
         # make sure schema is up to date:
         self.dbapi.execute("""CREATE TABLE IF NOT EXISTS person (
                                     handle    TEXT PRIMARY KEY NOT NULL,
@@ -1990,6 +2008,13 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         _LOG.debug("Write database backend file to 'dbapi'")
         with open(versionpath, "w") as version_file:
             version_file.write("dbapi")
+        # Write default_settings, sqlite.db
+        defaults = os.path.join(os.path.dirname(os.path.abspath(__file__)),
+                                "dbapi_support", "defaults")
+        _LOG.debug("Copy defaults from: " + defaults)
+        for filename in os.listdir(defaults):
+            fullpath = os.path.abspath(os.path.join(defaults, filename))
+            shutil.copy2(fullpath, directory)
 
     def report_bm_change(self):
         """
diff --git a/gramps/plugins/database/dbapi_support/defaults/default_settings.py b/gramps/plugins/database/dbapi_support/defaults/default_settings.py
new file mode 100644
index 000000000..8931c8e89
--- /dev/null
+++ b/gramps/plugins/database/dbapi_support/defaults/default_settings.py
@@ -0,0 +1,7 @@
+import os
+import sqlite3
+
+path_to_db = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                        'sqlite.db')  
+dbapi = sqlite3.connect(path_to_db)
+dbapi.row_factory = sqlite3.Row # allows access by name

From 3cd6622c8fce463c1fdee262afe25b128eaef3c0 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 23 May 2015 09:54:04 -0400
Subject: [PATCH 038/105] Added support for sort_handles

---
 gramps/plugins/database/dbapi.py | 162 ++++++++++++++++++++++---------
 1 file changed, 114 insertions(+), 48 deletions(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index c99146b6d..3dd90ac49 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -38,16 +38,8 @@ from gramps.gen.db import (PERSON_KEY,
 
 from gramps.gen.utils.id import create_id
 from gramps.gen.lib.researcher import Researcher
-from gramps.gen.lib.mediaobj import MediaObject
-from gramps.gen.lib.person import Person
-from gramps.gen.lib.family import Family
-from gramps.gen.lib.src import Source
-from gramps.gen.lib.citation import Citation
-from gramps.gen.lib.event import Event
-from gramps.gen.lib.place import Place
-from gramps.gen.lib.repo import Repository
-from gramps.gen.lib.note import Note
-from gramps.gen.lib.tag import Tag
+from gramps.gen.lib import (Tag, MediaObject, Person, Family, Source, Citation, Event,
+                            Place, Repository, Note, NameOriginType)
 from gramps.gen.lib.genderstats import GenderStats
 
 _LOG = logging.getLogger(DBLOGNAME)
@@ -761,62 +753,70 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return None
 
     def get_person_handles(self, sort_handles=False):
-        ## Fixme: implement sort
-        cur = self.dbapi.execute("select handle from person;")
+        if sort_handles:
+            cur = self.dbapi.execute("SELECT handle FROM person ORDER BY order_by;")
+        else:
+            cur = self.dbapi.execute("SELECT handle FROM person;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
-    def get_family_handles(self, sort_handles=False):
-        ## Fixme: implement sort
+    def get_family_handles(self):
         cur = self.dbapi.execute("select handle from family;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
-    def get_event_handles(self, sort_handles=False):
-        ## Fixme: implement sort
+    def get_event_handles(self):
         cur = self.dbapi.execute("select handle from event;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_citation_handles(self, sort_handles=False):
-        ## Fixme: implement sort
-        cur = self.dbapi.execute("select handle from citation;")
+        if sort_handles:
+            cur = self.dbapi.execute("select handle from citation ORDER BY order_by;")
+        else:
+            cur = self.dbapi.execute("select handle from citation;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_source_handles(self, sort_handles=False):
-        ## Fixme: implement sort
-        cur = self.dbapi.execute("select handle from source;")
+        if sort_handles:
+            cur = self.dbapi.execute("select handle from source ORDER BY order_by;")
+        else:
+            cur = self.dbapi.execute("select handle from source;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_place_handles(self, sort_handles=False):
-        ## Fixme: implement sort
-        cur = self.dbapi.execute("select handle from place;")
+        if sort_handles:
+            cur = self.dbapi.execute("select handle from place ORDER BY order_by;")
+        else:
+            cur = self.dbapi.execute("select handle from place;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
-    def get_repository_handles(self, sort_handles=False):
-        ## Fixme: implement sort
+    def get_repository_handles(self):
         cur = self.dbapi.execute("select handle from repository;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_media_object_handles(self, sort_handles=False):
-        ## Fixme: implement sort
-        cur = self.dbapi.execute("select handle from media;")
+        if sort_handles:
+            cur = self.dbapi.execute("select handle from media ORDER BY order_by;")
+        else:
+            cur = self.dbapi.execute("select handle from media;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
-    def get_note_handles(self, sort_handles=False):
-        ## Fixme: implement sort
+    def get_note_handles(self):
         cur = self.dbapi.execute("select handle from note;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_tag_handles(self, sort_handles=False):
-        # FIXME: implement sort
-        cur = self.dbapi.execute("select handle from tag;")
+        if sort_handles:
+            cur = self.dbapi.execute("select handle from tag ORDER BY order_by;")
+        else:
+            cur = self.dbapi.execute("select handle from tag;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
@@ -1252,16 +1252,21 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             if person.handle in self.person_map:
                 emit = "person-update"
                 self.dbapi.execute("""UPDATE person SET gramps_id = ?, 
+                                                        order_by = ?,
                                                         blob = ? 
                                                     WHERE handle = ?;""",
                                    [person.gramps_id, 
+                                    self._order_by_person_key(person),
                                     pickle.dumps(person.serialize()),
                                     person.handle])
             else:
                 emit = "person-add"
-                self.dbapi.execute("""insert into person(handle, gramps_id, blob) 
-                                                  values(?, ?, ?);""", 
-                                   [person.handle, person.gramps_id, pickle.dumps(person.serialize())])
+                self.dbapi.execute("""insert into person(handle, order_by, gramps_id, blob) 
+                                                  values(?, ?, ?, ?);""", 
+                                   [person.handle, 
+                                    self._order_by_person_key(person),
+                                    person.gramps_id, 
+                                    pickle.dumps(person.serialize())])
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1294,15 +1299,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             if citation.handle in self.citation_map:
                 emit = "citation-update"
                 self.dbapi.execute("""UPDATE citation SET gramps_id = ?, 
-                                                        blob = ? 
+                                                          order_by = ?,
+                                                          blob = ? 
                                                     WHERE handle = ?;""",
                                    [citation.gramps_id, 
+                                    self._order_by_citation_key(citation),
                                     pickle.dumps(citation.serialize()),
                                     citation.handle])
             else:
                 emit = "citation-add"
-                self.dbapi.execute("insert into citation values(?, ?, ?);", 
-                           [citation.handle, citation.gramps_id, pickle.dumps(citation.serialize())])
+                self.dbapi.execute("insert into citation values(?, ?, ?, ?);", 
+                           [citation.handle, 
+                            self._order_by_citation_key(citation),
+                            citation.gramps_id, 
+                            pickle.dumps(citation.serialize())])
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1314,15 +1324,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             if source.handle in self.source_map:
                 emit = "source-update"
                 self.dbapi.execute("""UPDATE source SET gramps_id = ?, 
+                                                        order_by = ?,
                                                         blob = ? 
                                                     WHERE handle = ?;""",
                                    [source.gramps_id, 
+                                    self._order_by_source_key(source),
                                     pickle.dumps(source.serialize()),
                                     source.handle])
             else:
                 emit = "source-add"
-                self.dbapi.execute("insert into source values(?, ?, ?);", 
-                           [source.handle, source.gramps_id, pickle.dumps(source.serialize())])
+                self.dbapi.execute("insert into source values(?, ?, ?, ?);", 
+                           [source.handle, 
+                            self._order_by_source_key(source),
+                            source.gramps_id, 
+                            pickle.dumps(source.serialize())])
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1374,15 +1389,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             if place.handle in self.place_map:
                 emit = "place-update"
                 self.dbapi.execute("""UPDATE place SET gramps_id = ?, 
-                                                        blob = ? 
+                                                       order_by = ?,
+                                                       blob = ? 
                                                     WHERE handle = ?;""",
                                    [place.gramps_id, 
+                                    self._order_by_place_key(place),
                                     pickle.dumps(place.serialize()),
                                     place.handle])
             else:
                 emit = "place-add"
-                self.dbapi.execute("insert into place values(?, ?, ?);", 
-                           [place.handle, place.gramps_id, pickle.dumps(place.serialize())])
+                self.dbapi.execute("insert into place values(?, ?, ?, ?);", 
+                           [place.handle, 
+                            self._order_by_place_key(place),
+                            place.gramps_id, 
+                            pickle.dumps(place.serialize())])
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1402,7 +1422,9 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             else:
                 emit = "event-add"
                 self.dbapi.execute("insert into event values(?, ?, ?);", 
-                           [event.handle, event.gramps_id, pickle.dumps(event.serialize())])
+                           [event.handle, 
+                            event.gramps_id, 
+                            pickle.dumps(event.serialize())])
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1413,14 +1435,18 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if True or not trans.batch:
             if tag.handle in self.tag_map:
                 emit = "tag-update"
-                self.dbapi.execute("""UPDATE tag SET blob = ? 
+                self.dbapi.execute("""UPDATE tag SET blob = ?,
+                                                     order_by = ?
                                              WHERE handle = ?;""",
                                    [pickle.dumps(tag.serialize()),
+                                    self._order_by_tag_key(tag),
                                     tag.handle])
             else:
                 emit = "tag-add"
-                self.dbapi.execute("insert into tag values(?, ?);", 
-                           [tag.handle, pickle.dumps(tag.serialize())])
+                self.dbapi.execute("insert into tag values(?, ?, ?);", 
+                           [tag.handle, 
+                            self._order_by_tag_key(tag),
+                            pickle.dumps(tag.serialize())])
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1432,15 +1458,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             if media.handle in self.media_map:
                 emit = "media-update"
                 self.dbapi.execute("""UPDATE media SET gramps_id = ?, 
-                                                        blob = ? 
+                                                       order_by = ?,
+                                                       blob = ? 
                                                     WHERE handle = ?;""",
                                    [media.gramps_id, 
+                                    self._order_by_media_key(media),
                                     pickle.dumps(media.serialize()),
                                     media.handle])
             else:
                 emit = "media-add"
-                self.dbapi.execute("insert into media values(?, ?, ?);", 
-                           [media.handle, media.gramps_id, pickle.dumps(media.serialize())])
+                self.dbapi.execute("insert into media values(?, ?, ?, ?);", 
+                           [media.handle, 
+                            self._order_by_media_key(media),
+                            media.gramps_id, 
+                            pickle.dumps(media.serialize())])
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1931,6 +1962,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # make sure schema is up to date:
         self.dbapi.execute("""CREATE TABLE IF NOT EXISTS person (
                                     handle    TEXT PRIMARY KEY NOT NULL,
+                                    order_by  TEXT             ,
                                     gramps_id TEXT             ,
                                     blob      TEXT
         );""")
@@ -1941,11 +1973,13 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         );""")
         self.dbapi.execute("""CREATE TABLE IF NOT EXISTS source (
                                     handle    TEXT PRIMARY KEY NOT NULL,
+                                    order_by  TEXT             ,
                                     gramps_id TEXT             ,
                                     blob      TEXT
         );""")
         self.dbapi.execute("""CREATE TABLE IF NOT EXISTS citation (
                                     handle    TEXT PRIMARY KEY NOT NULL,
+                                    order_by  TEXT             ,
                                     gramps_id TEXT             ,
                                     blob      TEXT
         );""")
@@ -1956,11 +1990,13 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         );""")
         self.dbapi.execute("""CREATE TABLE IF NOT EXISTS media (
                                     handle    TEXT PRIMARY KEY NOT NULL,
+                                    order_by  TEXT             ,
                                     gramps_id TEXT             ,
                                     blob      TEXT
         );""")
         self.dbapi.execute("""CREATE TABLE IF NOT EXISTS place (
                                     handle    TEXT PRIMARY KEY NOT NULL,
+                                    order_by  TEXT             ,
                                     gramps_id TEXT             ,
                                     blob      TEXT
         );""")
@@ -1976,6 +2012,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         );""")
         self.dbapi.execute("""CREATE TABLE IF NOT EXISTS tag (
                                     handle    TEXT PRIMARY KEY NOT NULL,
+                                    order_by  TEXT             ,
                                     blob      TEXT
         );""")
 
@@ -2254,3 +2291,32 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if row:
             return pickle.loads(row[0])
 
+    def _order_by_person_key(self, person):
+        """
+        All non pa/matronymic surnames are used in indexing.
+        pa/matronymic not as they change for every generation!
+        returns a byte string
+        """
+        if person.primary_name and person.primary_name.surname_list:
+            order_by = " ".join([x.surname for x in person.primary_name.surname_list if not 
+                                 (int(x.origintype) in [NameOriginType.PATRONYMIC, 
+                                                        NameOriginType.MATRONYMIC]) ])
+        else:
+            order_by = ""
+        return glocale.sort_key(order_by)
+
+    def _order_by_place_key(self, place):
+        return glocale.sort_key(place.title)
+
+    def _order_by_source_key(self, source):
+        return glocale.sort_key(source.title)
+
+    def _order_by_citation_key(self, citation):
+        return glocale.sort_key(citation.page)
+
+    def _order_by_media_key(self, media):
+        return glocale.sort_key(media.desc)
+
+    def _order_by_tag_key(self, tag):
+        return glocale.sort_key(tag.get_name())
+

From f275843556d687ee79414af316ff0c7f29c41b13 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 23 May 2015 10:07:57 -0400
Subject: [PATCH 039/105] Now using batch transactions

---
 gramps/plugins/database/dbapi.py | 328 +++++++++++++++----------------
 1 file changed, 163 insertions(+), 165 deletions(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index 3dd90ac49..774a4bdda 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -495,16 +495,14 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return None
 
     def transaction_commit(self, txn):
-        ## FIXME
-        pass
+        self.dbapi.commit()
 
     def get_undodb(self):
         ## FIXME
         return None
 
     def transaction_abort(self, txn):
-        ## FIXME
-        pass
+        self.dbapi.rollback()
 
     @staticmethod
     def _validated_id_prefix(val, default):
@@ -1248,25 +1246,25 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def commit_person(self, person, trans, change_time=None):
         emit = None
-        if True or not trans.batch:
-            if person.handle in self.person_map:
-                emit = "person-update"
-                self.dbapi.execute("""UPDATE person SET gramps_id = ?, 
-                                                        order_by = ?,
-                                                        blob = ? 
-                                                    WHERE handle = ?;""",
-                                   [person.gramps_id, 
-                                    self._order_by_person_key(person),
-                                    pickle.dumps(person.serialize()),
-                                    person.handle])
-            else:
-                emit = "person-add"
-                self.dbapi.execute("""insert into person(handle, order_by, gramps_id, blob) 
-                                                  values(?, ?, ?, ?);""", 
-                                   [person.handle, 
-                                    self._order_by_person_key(person),
-                                    person.gramps_id, 
-                                    pickle.dumps(person.serialize())])
+        if person.handle in self.person_map:
+            emit = "person-update"
+            self.dbapi.execute("""UPDATE person SET gramps_id = ?, 
+                                                    order_by = ?,
+                                                    blob = ? 
+                                                WHERE handle = ?;""",
+                               [person.gramps_id, 
+                                self._order_by_person_key(person),
+                                pickle.dumps(person.serialize()),
+                                person.handle])
+        else:
+            emit = "person-add"
+            self.dbapi.execute("""insert into person(handle, order_by, gramps_id, blob) 
+                                              values(?, ?, ?, ?);""", 
+                               [person.handle, 
+                                self._order_by_person_key(person),
+                                person.gramps_id, 
+                                pickle.dumps(person.serialize())])
+        if not trans.batch:
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1274,20 +1272,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def commit_family(self, family, trans, change_time=None):
         emit = None
-        if True or not trans.batch:
-            if family.handle in self.family_map:
-                emit = "family-update"
-                self.dbapi.execute("""UPDATE family SET gramps_id = ?, 
-                                                        blob = ? 
-                                                    WHERE handle = ?;""",
-                                   [family.gramps_id, 
-                                    pickle.dumps(family.serialize()),
-                                    family.handle])
-            else:
-                emit = "family-add"
-                self.dbapi.execute("insert into family values(?, ?, ?);", 
-                                   [family.handle, family.gramps_id, 
-                                    pickle.dumps(family.serialize())])
+        if family.handle in self.family_map:
+            emit = "family-update"
+            self.dbapi.execute("""UPDATE family SET gramps_id = ?, 
+                                                    blob = ? 
+                                                WHERE handle = ?;""",
+                               [family.gramps_id, 
+                                pickle.dumps(family.serialize()),
+                                family.handle])
+        else:
+            emit = "family-add"
+            self.dbapi.execute("insert into family values(?, ?, ?);", 
+                               [family.handle, family.gramps_id, 
+                                pickle.dumps(family.serialize())])
+        if not trans.batch:
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1295,24 +1293,24 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def commit_citation(self, citation, trans, change_time=None):
         emit = None
-        if True or not trans.batch:
-            if citation.handle in self.citation_map:
-                emit = "citation-update"
-                self.dbapi.execute("""UPDATE citation SET gramps_id = ?, 
-                                                          order_by = ?,
-                                                          blob = ? 
-                                                    WHERE handle = ?;""",
-                                   [citation.gramps_id, 
-                                    self._order_by_citation_key(citation),
-                                    pickle.dumps(citation.serialize()),
-                                    citation.handle])
-            else:
-                emit = "citation-add"
-                self.dbapi.execute("insert into citation values(?, ?, ?, ?);", 
-                           [citation.handle, 
-                            self._order_by_citation_key(citation),
-                            citation.gramps_id, 
-                            pickle.dumps(citation.serialize())])
+        if citation.handle in self.citation_map:
+            emit = "citation-update"
+            self.dbapi.execute("""UPDATE citation SET gramps_id = ?, 
+                                                      order_by = ?,
+                                                      blob = ? 
+                                                WHERE handle = ?;""",
+                               [citation.gramps_id, 
+                                self._order_by_citation_key(citation),
+                                pickle.dumps(citation.serialize()),
+                                citation.handle])
+        else:
+            emit = "citation-add"
+            self.dbapi.execute("insert into citation values(?, ?, ?, ?);", 
+                       [citation.handle, 
+                        self._order_by_citation_key(citation),
+                        citation.gramps_id, 
+                        pickle.dumps(citation.serialize())])
+        if not trans.batch:
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1320,24 +1318,24 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def commit_source(self, source, trans, change_time=None):
         emit = None
-        if True or not trans.batch:
-            if source.handle in self.source_map:
-                emit = "source-update"
-                self.dbapi.execute("""UPDATE source SET gramps_id = ?, 
-                                                        order_by = ?,
-                                                        blob = ? 
-                                                    WHERE handle = ?;""",
-                                   [source.gramps_id, 
-                                    self._order_by_source_key(source),
-                                    pickle.dumps(source.serialize()),
-                                    source.handle])
-            else:
-                emit = "source-add"
-                self.dbapi.execute("insert into source values(?, ?, ?, ?);", 
-                           [source.handle, 
-                            self._order_by_source_key(source),
-                            source.gramps_id, 
-                            pickle.dumps(source.serialize())])
+        if source.handle in self.source_map:
+            emit = "source-update"
+            self.dbapi.execute("""UPDATE source SET gramps_id = ?, 
+                                                    order_by = ?,
+                                                    blob = ? 
+                                                WHERE handle = ?;""",
+                               [source.gramps_id, 
+                                self._order_by_source_key(source),
+                                pickle.dumps(source.serialize()),
+                                source.handle])
+        else:
+            emit = "source-add"
+            self.dbapi.execute("insert into source values(?, ?, ?, ?);", 
+                       [source.handle, 
+                        self._order_by_source_key(source),
+                        source.gramps_id, 
+                        pickle.dumps(source.serialize())])
+        if not trans.batch:
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1345,19 +1343,19 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def commit_repository(self, repository, trans, change_time=None):
         emit = None
-        if True or not trans.batch:
-            if repository.handle in self.repository_map:
-                emit = "repository-update"
-                self.dbapi.execute("""UPDATE repository SET gramps_id = ?, 
-                                                        blob = ? 
-                                                    WHERE handle = ?;""",
-                                   [repository.gramps_id, 
-                                    pickle.dumps(repository.serialize()),
-                                    repository.handle])
-            else:
-                emit = "repository-add"
-                self.dbapi.execute("insert into repository values(?, ?, ?);", 
-                           [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())])
+        if repository.handle in self.repository_map:
+            emit = "repository-update"
+            self.dbapi.execute("""UPDATE repository SET gramps_id = ?, 
+                                                    blob = ? 
+                                                WHERE handle = ?;""",
+                               [repository.gramps_id, 
+                                pickle.dumps(repository.serialize()),
+                                repository.handle])
+        else:
+            emit = "repository-add"
+            self.dbapi.execute("insert into repository values(?, ?, ?);", 
+                       [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())])
+        if not trans.batch:
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1365,19 +1363,19 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def commit_note(self, note, trans, change_time=None):
         emit = None
-        if True or not trans.batch:
-            if note.handle in self.note_map:
-                emit = "note-update"
-                self.dbapi.execute("""UPDATE note SET gramps_id = ?, 
-                                                        blob = ? 
-                                                    WHERE handle = ?;""",
-                                   [note.gramps_id, 
-                                    pickle.dumps(note.serialize()),
-                                    note.handle])
-            else:
-                emit = "note-add"
-                self.dbapi.execute("insert into note values(?, ?, ?);", 
-                           [note.handle, note.gramps_id, pickle.dumps(note.serialize())])
+        if note.handle in self.note_map:
+            emit = "note-update"
+            self.dbapi.execute("""UPDATE note SET gramps_id = ?, 
+                                                    blob = ? 
+                                                WHERE handle = ?;""",
+                               [note.gramps_id, 
+                                pickle.dumps(note.serialize()),
+                                note.handle])
+        else:
+            emit = "note-add"
+            self.dbapi.execute("insert into note values(?, ?, ?);", 
+                       [note.handle, note.gramps_id, pickle.dumps(note.serialize())])
+        if not trans.batch:
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1385,24 +1383,24 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def commit_place(self, place, trans, change_time=None):
         emit = None
-        if True or not trans.batch:
-            if place.handle in self.place_map:
-                emit = "place-update"
-                self.dbapi.execute("""UPDATE place SET gramps_id = ?, 
-                                                       order_by = ?,
-                                                       blob = ? 
-                                                    WHERE handle = ?;""",
-                                   [place.gramps_id, 
-                                    self._order_by_place_key(place),
-                                    pickle.dumps(place.serialize()),
-                                    place.handle])
-            else:
-                emit = "place-add"
-                self.dbapi.execute("insert into place values(?, ?, ?, ?);", 
-                           [place.handle, 
-                            self._order_by_place_key(place),
-                            place.gramps_id, 
-                            pickle.dumps(place.serialize())])
+        if place.handle in self.place_map:
+            emit = "place-update"
+            self.dbapi.execute("""UPDATE place SET gramps_id = ?, 
+                                                   order_by = ?,
+                                                   blob = ? 
+                                                WHERE handle = ?;""",
+                               [place.gramps_id, 
+                                self._order_by_place_key(place),
+                                pickle.dumps(place.serialize()),
+                                place.handle])
+        else:
+            emit = "place-add"
+            self.dbapi.execute("insert into place values(?, ?, ?, ?);", 
+                       [place.handle, 
+                        self._order_by_place_key(place),
+                        place.gramps_id, 
+                        pickle.dumps(place.serialize())])
+        if not trans.batch:
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1410,21 +1408,21 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def commit_event(self, event, trans, change_time=None):
         emit = None
-        if True or not trans.batch:
-            if event.handle in self.event_map:
-                emit = "event-update"
-                self.dbapi.execute("""UPDATE event SET gramps_id = ?, 
-                                                        blob = ? 
-                                                    WHERE handle = ?;""",
-                                   [event.gramps_id, 
-                                    pickle.dumps(event.serialize()),
-                                    event.handle])
-            else:
-                emit = "event-add"
-                self.dbapi.execute("insert into event values(?, ?, ?);", 
-                           [event.handle, 
-                            event.gramps_id, 
-                            pickle.dumps(event.serialize())])
+        if event.handle in self.event_map:
+            emit = "event-update"
+            self.dbapi.execute("""UPDATE event SET gramps_id = ?, 
+                                                    blob = ? 
+                                                WHERE handle = ?;""",
+                               [event.gramps_id, 
+                                pickle.dumps(event.serialize()),
+                                event.handle])
+        else:
+            emit = "event-add"
+            self.dbapi.execute("insert into event values(?, ?, ?);", 
+                       [event.handle, 
+                        event.gramps_id, 
+                        pickle.dumps(event.serialize())])
+        if not trans.batch:
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1432,21 +1430,21 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def commit_tag(self, tag, trans, change_time=None):
         emit = None
-        if True or not trans.batch:
-            if tag.handle in self.tag_map:
-                emit = "tag-update"
-                self.dbapi.execute("""UPDATE tag SET blob = ?,
-                                                     order_by = ?
-                                             WHERE handle = ?;""",
-                                   [pickle.dumps(tag.serialize()),
-                                    self._order_by_tag_key(tag),
-                                    tag.handle])
-            else:
-                emit = "tag-add"
-                self.dbapi.execute("insert into tag values(?, ?, ?);", 
-                           [tag.handle, 
-                            self._order_by_tag_key(tag),
-                            pickle.dumps(tag.serialize())])
+        if tag.handle in self.tag_map:
+            emit = "tag-update"
+            self.dbapi.execute("""UPDATE tag SET blob = ?,
+                                                 order_by = ?
+                                         WHERE handle = ?;""",
+                               [pickle.dumps(tag.serialize()),
+                                self._order_by_tag_key(tag),
+                                tag.handle])
+        else:
+            emit = "tag-add"
+            self.dbapi.execute("insert into tag values(?, ?, ?);", 
+                       [tag.handle, 
+                        self._order_by_tag_key(tag),
+                        pickle.dumps(tag.serialize())])
+        if not trans.batch:
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1454,24 +1452,24 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def commit_media_object(self, media, trans, change_time=None):
         emit = None
-        if True or not trans.batch:
-            if media.handle in self.media_map:
-                emit = "media-update"
-                self.dbapi.execute("""UPDATE media SET gramps_id = ?, 
-                                                       order_by = ?,
-                                                       blob = ? 
-                                                    WHERE handle = ?;""",
-                                   [media.gramps_id, 
-                                    self._order_by_media_key(media),
-                                    pickle.dumps(media.serialize()),
-                                    media.handle])
-            else:
-                emit = "media-add"
-                self.dbapi.execute("insert into media values(?, ?, ?, ?);", 
-                           [media.handle, 
-                            self._order_by_media_key(media),
-                            media.gramps_id, 
-                            pickle.dumps(media.serialize())])
+        if media.handle in self.media_map:
+            emit = "media-update"
+            self.dbapi.execute("""UPDATE media SET gramps_id = ?, 
+                                                   order_by = ?,
+                                                   blob = ? 
+                                                WHERE handle = ?;""",
+                               [media.gramps_id, 
+                                self._order_by_media_key(media),
+                                pickle.dumps(media.serialize()),
+                                media.handle])
+        else:
+            emit = "media-add"
+            self.dbapi.execute("insert into media values(?, ?, ?, ?);", 
+                       [media.handle, 
+                        self._order_by_media_key(media),
+                        media.gramps_id, 
+                        pickle.dumps(media.serialize())])
+        if not trans.batch:
             self.dbapi.commit()
         # Emit after added:
         if emit:

From aa7928d35d3797e8740b7d1cfa514a950e277b0a Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 23 May 2015 10:27:49 -0400
Subject: [PATCH 040/105] Added indices on order_by fields

---
 gramps/plugins/database/dbapi.py | 23 ++++++++++++++++++++++-
 1 file changed, 22 insertions(+), 1 deletion(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index 774a4bdda..436ba87d5 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -1490,7 +1490,9 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return list(key2table[obj_key].keys())
 
     def transaction_begin(self, transaction):
-        ## FIXME
+        """
+        Transactions are handled automatically by the db layer.
+        """
         return 
 
     def set_researcher(self, owner):
@@ -2013,6 +2015,25 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                     order_by  TEXT             ,
                                     blob      TEXT
         );""")
+        ## Indices:
+        self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
+                                  order_by ON person (order_by);
+        """)
+        self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
+                                  order_by ON source (order_by);
+        """)
+        self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
+                                  order_by ON citation (order_by);
+        """)
+        self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
+                                  order_by ON media (order_by);
+        """)
+        self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
+                                  order_by ON place (order_by);
+        """)
+        self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
+                                  order_by ON tag (order_by);
+        """)
 
     def redo(self, update_history=True):
         ## FIXME

From 56cf1b02abb4819a86a4cbb68cca52eebf6eba0d Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 23 May 2015 14:52:43 -0400
Subject: [PATCH 041/105] Basics for back references now work, although doesn't
 update with edits yet

---
 gramps/plugins/database/dbapi.py | 145 +++++++++++++++++--------------
 1 file changed, 79 insertions(+), 66 deletions(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index 436ba87d5..31e50534c 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -20,7 +20,8 @@ import gramps
 from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
 from gramps.gen.db import (DbReadBase, DbWriteBase, DbTxn, 
-                           KEY_TO_NAME_MAP, KEY_TO_CLASS_MAP)
+                           KEY_TO_NAME_MAP, KEY_TO_CLASS_MAP, 
+                           CLASS_TO_KEY_MAP)
 from gramps.gen.utils.callback import Callback
 from gramps.gen.updatecallback import UpdateCallback
 from gramps.gen.db.undoredo import DbUndo
@@ -1245,6 +1246,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return obj.handle
 
     def commit_person(self, person, trans, change_time=None):
+        ## FIXME: update reference, for back references
         emit = None
         if person.handle in self.person_map:
             emit = "person-update"
@@ -1271,6 +1273,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.emit(emit, ([person.handle],))
 
     def commit_family(self, family, trans, change_time=None):
+        ## FIXME: update reference, for back references
         emit = None
         if family.handle in self.family_map:
             emit = "family-update"
@@ -1292,6 +1295,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.emit(emit, ([family.handle],))
 
     def commit_citation(self, citation, trans, change_time=None):
+        ## FIXME: update reference, for back references
         emit = None
         if citation.handle in self.citation_map:
             emit = "citation-update"
@@ -1317,6 +1321,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.emit(emit, ([citation.handle],))
 
     def commit_source(self, source, trans, change_time=None):
+        ## FIXME: update reference, for back references
         emit = None
         if source.handle in self.source_map:
             emit = "source-update"
@@ -1342,6 +1347,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.emit(emit, ([source.handle],))
 
     def commit_repository(self, repository, trans, change_time=None):
+        ## FIXME: update reference, for back references
         emit = None
         if repository.handle in self.repository_map:
             emit = "repository-update"
@@ -1362,6 +1368,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.emit(emit, ([repository.handle],))
 
     def commit_note(self, note, trans, change_time=None):
+        ## FIXME: update reference, for back references
         emit = None
         if note.handle in self.note_map:
             emit = "note-update"
@@ -1382,6 +1389,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.emit(emit, ([note.handle],))
 
     def commit_place(self, place, trans, change_time=None):
+        ## FIXME: update reference, for back references
         emit = None
         if place.handle in self.place_map:
             emit = "place-update"
@@ -1407,6 +1415,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.emit(emit, ([place.handle],))
 
     def commit_event(self, event, trans, change_time=None):
+        ## FIXME: update reference, for back references
         emit = None
         if event.handle in self.event_map:
             emit = "event-update"
@@ -1429,6 +1438,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.emit(emit, ([event.handle],))
 
     def commit_tag(self, tag, trans, change_time=None):
+        ## FIXME: update reference, for back references
         emit = None
         if tag.handle in self.tag_map:
             emit = "tag-update"
@@ -1451,6 +1461,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.emit(emit, ([tag.handle],))
 
     def commit_media_object(self, media, trans, change_time=None):
+        ## FIXME: update reference, for back references
         emit = None
         if media.handle in self.media_map:
             emit = "media-update"
@@ -1675,64 +1686,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                [handle])
             self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],))
 
-    def delete_primary_from_reference_map(self, handle, transaction, txn=None):
-        """
-        Remove all references to the primary object from the reference_map.
-        handle should be utf-8
-        """
-        primary_cur = self.get_reference_map_primary_cursor()
-
-        try:
-            ret = primary_cur.set(handle)
-        except:
-            ret = None
-        
-        remove_list = set()
-        while (ret is not None):
-            (key, data) = ret
-            
-            # data values are of the form:
-            #   ((primary_object_class_name, primary_object_handle),
-            #    (referenced_object_class_name, referenced_object_handle))
-            
-            # so we need the second tuple give us a reference that we can
-            # combine with the primary_handle to get the main key.
-            main_key = (handle.decode('utf-8'), pickle.loads(data)[1][1])
-            
-            # The trick is not to remove while inside the cursor,
-            # but collect them all and remove after the cursor is closed
-            remove_list.add(main_key)
-
-            ret = primary_cur.next_dup()
-
-        primary_cur.close()
-
-        # Now that the cursor is closed, we can remove things
-        for main_key in remove_list:
-            self.__remove_reference(main_key, transaction, txn)
-
-    def __remove_reference(self, key, transaction, txn):
-        """
-        Remove the reference specified by the key, preserving the change in 
-        the passed transaction.
-        """
-        if isinstance(key, tuple):
-            #create a byte string key, first validity check in python 3!
-            for val in key:
-                if isinstance(val, bytes):
-                    raise DbError(_('An attempt is made to save a reference key '
-                        'which is partly bytecode, this is not allowed.\n'
-                        'Key is %s') % str(key))
-            key = str(key)
-        if isinstance(key, str):
-            key = key.encode('utf-8')
-        if not self.readonly:
-            if not transaction.batch:
-                old_data = self.reference_map.get(key, txn=txn)
-                transaction.add(REFERENCE_KEY, TXNDEL, key, old_data, None)
-                #transaction.reference_del.append(str(key))
-            self.reference_map.delete(key, txn=txn)
-
     ## Missing:
 
     def backup(self):
@@ -1751,8 +1704,28 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.dbapi.close()
 
     def find_backlink_handles(self, handle, include_classes=None):
-        ## FIXME
-        return []
+        """
+        Find all objects that hold a reference to the object handle.
+        
+        Returns an interator over a list of (class_name, handle) tuples.
+
+        :param handle: handle of the object to search for.
+        :type handle: database handle
+        :param include_classes: list of class names to include in the results.
+            Default: None means include all classes.
+        :type include_classes: list of class names
+
+        Note that this is a generator function, it returns a iterator for
+        use in loops. If you want a list of the results use::
+
+            result_list = list(find_backlink_handles(handle))
+        """
+        cur = self.dbapi.execute("SELECT * from reference WHERE ref_handle = ?;",
+                                 [handle])
+        rows = cur.fetchall()
+        for row in rows:
+            if (include_classes is None) or (row["obj_class"] in include_classes):
+                yield (row["obj_class"], row["obj_handle"])
 
     def find_initial_person(self):
         items = self.person_map.keys()
@@ -2015,6 +1988,13 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                     order_by  TEXT             ,
                                     blob      TEXT
         );""")
+        # Reference
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS reference (
+                                    obj_handle    TEXT,
+                                    obj_class     TEXT,
+                                    ref_handle    TEXT,
+                                    ref_class     TEXT
+        );""")
         ## Indices:
         self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
                                   order_by ON person (order_by);
@@ -2034,6 +2014,9 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
                                   order_by ON tag (order_by);
         """)
+        self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
+                                  ref_handle ON reference (ref_handle);
+        """)
 
     def redo(self, update_history=True):
         ## FIXME
@@ -2111,13 +2094,43 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             name = None
         return name
 
-    def reindex_reference_map(self):
-        ## FIXME
-        pass
+    def reindex_reference_map(self, callback):
+        callback(4)
+        self.dbapi.execute("DELETE FROM reference;")
+        primary_table = (
+            (self.get_person_cursor, Person),
+            (self.get_family_cursor, Family),
+            (self.get_event_cursor, Event),
+            (self.get_place_cursor, Place),
+            (self.get_source_cursor, Source),
+            (self.get_citation_cursor, Citation),
+            (self.get_media_cursor, MediaObject),
+            (self.get_repository_cursor, Repository),
+            (self.get_note_cursor, Note),
+            (self.get_tag_cursor, Tag),
+        )
+        # Now we use the functions and classes defined above
+        # to loop through each of the primary object tables.
+        for cursor_func, class_func in primary_table:
+            logging.info("Rebuilding %s reference map" %
+                         class_func.__name__)
+            with cursor_func() as cursor:
+                for found_handle, val in cursor:
+                    obj = class_func.create(val)
+                    references = set(obj.get_referenced_handles_recursively())
+                    # handle addition of new references
+                    for (ref_class_name, ref_handle) in references:
+                        self.dbapi.execute("INSERT into reference VALUES(?, ?, ?, ?);",
+                                           [obj.handle, 
+                                            obj.__class__.__name__,
+                                            ref_handle, 
+                                            ref_class_name])
+                                            
+        self.dbapi.commit()
+        callback(5)
 
     def rebuild_secondary(self, update):
-        ## FIXME
-        pass
+        self.reindex_reference_map(update)
 
     def prepare_import(self):
         """

From e4898df12d920c68dc3e4e714152d9848b3ee248 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 23 May 2015 20:25:04 -0400
Subject: [PATCH 042/105] WIP: name_group; clean up of SQL

---
 gramps/plugins/database/dbapi.py | 89 ++++++++++++++++++++------------
 1 file changed, 56 insertions(+), 33 deletions(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index 31e50534c..d501dbdd3 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -462,7 +462,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                  contains_func="has_gramps_id_func")
         self.tag_map  = Map(Table(self._tables["Tag"]))
         self.metadata   = Map(Table({"cursor_func": lambda: MetaCursor()}))
-        self.name_group = {}
+        ## FIXME: add appropriate methods:
+        self.name_group = Map(Table({"handles_func": self.get_name_group_keys,  # keys
+                                     "has_handle_func": self.has_name_group_key, # key in table 
+                                     "cursor_func": None, # create a cursor, values
+                                     "add_func": self.set_name_group_mapping, # add a key, value
+                                     "count_func": None})) # how many items?
         self.undo_callback = None
         self.redo_callback = None
         self.undo_history_callback = None
@@ -746,10 +751,16 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return None
 
     def get_name_group_keys(self):
-        return []
+        cur = self.dbapi.execute("select name from name_group order by name;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
 
     def get_name_group_mapping(self, key):
-        return None
+        cur = self.dbapi.execute("select grouping from name_group where name = ?;", 
+                                 [key])
+        row = cur.fetchone()
+        if row:
+            return row[0]
 
     def get_person_handles(self, sort_handles=False):
         if sort_handles:
@@ -1096,12 +1107,22 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return handle in self.media_map
 
     def has_name_group_key(self, key):
-        # FIXME:
-        return False
+        cur = self.dbapi.execute("select grouping from name_group where name = ?;", 
+                                 [key])
+        row = cur.fetchone()
+        return True if row else False
 
-    def set_name_group_mapping(self, key, value):
-        # FIXME:
-        pass
+    def set_name_group_mapping(self, name, grouping):
+        sname = name.encode("utf-8")
+        cur = self.dbapi.execute("SELECT * from name_group where name = ?;", 
+                                 [sname])
+        row = cur.fetchone()
+        if row:
+            cur = self.dbapi.execute("DELETE from name_group where name = ?;", 
+                                     [sname])
+        cur = self.dbapi.execute("INSERT into name_group (name, grouping) VALUES(?, ?);",
+                                 [sname, grouping])
+        self.dbapi.commit()
 
     def set_default_person_handle(self, handle):
         ## FIXME
@@ -1260,8 +1281,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 person.handle])
         else:
             emit = "person-add"
-            self.dbapi.execute("""insert into person(handle, order_by, gramps_id, blob) 
-                                              values(?, ?, ?, ?);""", 
+            self.dbapi.execute("""insert into person (handle, order_by, gramps_id, blob)
+                            values(?, ?, ?, ?);""", 
                                [person.handle, 
                                 self._order_by_person_key(person),
                                 person.gramps_id, 
@@ -1285,7 +1306,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 family.handle])
         else:
             emit = "family-add"
-            self.dbapi.execute("insert into family values(?, ?, ?);", 
+            self.dbapi.execute("""insert into family (handle, gramps_id, blob)
+                    values(?, ?, ?);""", 
                                [family.handle, family.gramps_id, 
                                 pickle.dumps(family.serialize())])
         if not trans.batch:
@@ -1309,7 +1331,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 citation.handle])
         else:
             emit = "citation-add"
-            self.dbapi.execute("insert into citation values(?, ?, ?, ?);", 
+            self.dbapi.execute("""insert into citation (handle, order_by, gramps_id, blob)
+                     values(?, ?, ?, ?);""", 
                        [citation.handle, 
                         self._order_by_citation_key(citation),
                         citation.gramps_id, 
@@ -1335,7 +1358,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 source.handle])
         else:
             emit = "source-add"
-            self.dbapi.execute("insert into source values(?, ?, ?, ?);", 
+            self.dbapi.execute("""insert into source (handle, order_by, gramps_id, blob)
+                    values(?, ?, ?, ?);""", 
                        [source.handle, 
                         self._order_by_source_key(source),
                         source.gramps_id, 
@@ -1359,7 +1383,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 repository.handle])
         else:
             emit = "repository-add"
-            self.dbapi.execute("insert into repository values(?, ?, ?);", 
+            self.dbapi.execute("""insert into repository (handle, gramps_id, blob)
+                     values(?, ?, ?);""", 
                        [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())])
         if not trans.batch:
             self.dbapi.commit()
@@ -1380,7 +1405,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 note.handle])
         else:
             emit = "note-add"
-            self.dbapi.execute("insert into note values(?, ?, ?);", 
+            self.dbapi.execute("""insert into note (handle, gramps_id, blob)
+                     values(?, ?, ?);""", 
                        [note.handle, note.gramps_id, pickle.dumps(note.serialize())])
         if not trans.batch:
             self.dbapi.commit()
@@ -1403,7 +1429,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 place.handle])
         else:
             emit = "place-add"
-            self.dbapi.execute("insert into place values(?, ?, ?, ?);", 
+            self.dbapi.execute("""insert into place (handle, order_by, gramps_id, blob)
+                    values(?, ?, ?, ?);""", 
                        [place.handle, 
                         self._order_by_place_key(place),
                         place.gramps_id, 
@@ -1427,7 +1454,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 event.handle])
         else:
             emit = "event-add"
-            self.dbapi.execute("insert into event values(?, ?, ?);", 
+            self.dbapi.execute("""insert into event (handle, gramps_id, blob)
+                  values(?, ?, ?);""", 
                        [event.handle, 
                         event.gramps_id, 
                         pickle.dumps(event.serialize())])
@@ -1450,7 +1478,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 tag.handle])
         else:
             emit = "tag-add"
-            self.dbapi.execute("insert into tag values(?, ?, ?);", 
+            self.dbapi.execute("""insert into tag (handle, order_by, blob)
+                  values(?, ?, ?);""", 
                        [tag.handle, 
                         self._order_by_tag_key(tag),
                         pickle.dumps(tag.serialize())])
@@ -1475,7 +1504,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 media.handle])
         else:
             emit = "media-add"
-            self.dbapi.execute("insert into media values(?, ?, ?, ?);", 
+            self.dbapi.execute("""insert into media (handle, order_by, gramps_id, blob)
+                  values(?, ?, ?, ?);""", 
                        [media.handle, 
                         self._order_by_media_key(media),
                         media.gramps_id, 
@@ -1747,18 +1777,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def get_citation_bookmarks(self):
         return self.citation_bookmarks
 
-    def get_cursor(self, table, txn=None, update=False, commit=False):
-        ## FIXME
-        ## called from a complete find_back_ref
-        pass
-
-    # cursors for lookups in the reference_map for back reference
-    # lookups. The reference_map has three indexes:
-    # the main index: a tuple of (primary_handle, referenced_handle)
-    # the primary_handle index: the primary_handle
-    # the referenced_handle index: the referenced_handle
-    # the main index is unique, the others allow duplicate entries.
-
     def get_default_handle(self):
         items = self.person_map.keys()
         if len(items) > 0:
@@ -1988,13 +2006,17 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                     order_by  TEXT             ,
                                     blob      TEXT
         );""")
-        # Reference
+        # Secondary:
         self.dbapi.execute("""CREATE TABLE IF NOT EXISTS reference (
                                     obj_handle    TEXT,
                                     obj_class     TEXT,
                                     ref_handle    TEXT,
                                     ref_class     TEXT
         );""")
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS name_group (
+                                    name     TEXT PRIMARY KEY NOT NULL,
+                                    grouping TEXT
+        );""")
         ## Indices:
         self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
                                   order_by ON person (order_by);
@@ -2120,7 +2142,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                     references = set(obj.get_referenced_handles_recursively())
                     # handle addition of new references
                     for (ref_class_name, ref_handle) in references:
-                        self.dbapi.execute("INSERT into reference VALUES(?, ?, ?, ?);",
+                        self.dbapi.execute("""INSERT into reference (obj_handle, obj_class, ref_handle, ref_class)
+                                                 VALUES(?, ?, ?, ?);""",
                                            [obj.handle, 
                                             obj.__class__.__name__,
                                             ref_handle, 

From e85b4be7d662e427ff687aeb15080dd5f827bde7 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 23 May 2015 21:27:51 -0400
Subject: [PATCH 043/105] Added metadata table and setting/value

---
 gramps/plugins/database/dbapi.py | 48 ++++++++++++++++++++++++++------
 1 file changed, 39 insertions(+), 9 deletions(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index d501dbdd3..503194bf9 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -748,7 +748,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return gid
 
     def get_mediapath(self):
-        return None
+        cur = self.dbapi.execute("select * from metadata where setting = ?", ["media-path"])
+        row = cur.fetchone()
+        if row:
+            return row["value"]
+        else:
+            return None
 
     def get_name_group_keys(self):
         cur = self.dbapi.execute("select name from name_group order by name;")
@@ -1125,12 +1130,27 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.dbapi.commit()
 
     def set_default_person_handle(self, handle):
-        ## FIXME
-        pass
+        cur = self.dbapi.execute("select * from metadata where setting = ?", ["default-person"])
+        row = cur.fetchone()
+        if row:
+            cur = self.dbapi.execute("UPDATE metadata SET value = ? WHERE setting = ?;", 
+                                     [handle, "default-person"])
+        else:
+            cur = self.dbapi.execute("INSERT INTO metadata (setting, value) VALUES (?, ?);", 
+                                     ["default-person", handle])
+        self.emit('home-person-changed')
+        self.dbapi.commit()
 
     def set_mediapath(self, mediapath):
-        ## FIXME
-        pass
+        cur = self.dbapi.execute("select * from metadata where setting = ?", ["media-path"])
+        row = cur.fetchone()
+        if row:
+            cur = self.dbapi.execute("UPDATE metadata SET value = ? WHERE setting = ?;", 
+                                     [mediapath, "media-path"])
+        else:
+            cur = self.dbapi.execute("INSERT INTO metadata (setting, value) VALUES (?, ?);", 
+                                     ["media-path", mediapath])
+        self.dbapi.commit()
 
     def get_raw_person_data(self, handle):
         if handle in self.person_map:
@@ -1615,6 +1635,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             #del self.person_id_map[person.gramps_id]
             self.dbapi.execute("DELETE from person WHERE handle = ?;", [handle])
             self.emit("person-delete", ([handle],))
+            if not transaction.batch:
+                self.dbapi.commit()
 
     def remove_source(self, handle, transaction):
         """
@@ -1715,6 +1737,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.dbapi.execute("DELETE from %s WHERE handle = ?;" % key2table[key], 
                                [handle])
             self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],))
+            if not transaction.batch:
+                self.dbapi.commit()
 
     ## Missing:
 
@@ -1778,10 +1802,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return self.citation_bookmarks
 
     def get_default_handle(self):
-        items = self.person_map.keys()
-        if len(items) > 0:
-            return list(items)[0]
-        return None
+        cur = self.dbapi.execute("select * from metadata where setting = ?", ["default-person"])
+        row = cur.fetchone()
+        if row:
+            return row["value"]
+        else:
+            return None
 
     def get_event_attribute_types(self):
         ## FIXME
@@ -2017,6 +2043,10 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                     name     TEXT PRIMARY KEY NOT NULL,
                                     grouping TEXT
         );""")
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS metadata (
+                                    setting  TEXT PRIMARY KEY NOT NULL,
+                                    value    TEXT
+        );""")
         ## Indices:
         self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
                                   order_by ON person (order_by);

From 80b2b351e696f8da6f282e870766f8291e612f27 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 23 May 2015 22:46:05 -0400
Subject: [PATCH 044/105] Update backlinks

---
 gramps/plugins/database/dbapi.py | 55 +++++++++++++++++++++++++-------
 1 file changed, 44 insertions(+), 11 deletions(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index 503194bf9..d4a2bf09e 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -1287,7 +1287,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return obj.handle
 
     def commit_person(self, person, trans, change_time=None):
-        ## FIXME: update reference, for back references
         emit = None
         if person.handle in self.person_map:
             emit = "person-update"
@@ -1309,12 +1308,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 pickle.dumps(person.serialize())])
         if not trans.batch:
             self.dbapi.commit()
+            self.update_backlinks(person)
         # Emit after added:
         if emit:
             self.emit(emit, ([person.handle],))
 
     def commit_family(self, family, trans, change_time=None):
-        ## FIXME: update reference, for back references
         emit = None
         if family.handle in self.family_map:
             emit = "family-update"
@@ -1332,12 +1331,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 pickle.dumps(family.serialize())])
         if not trans.batch:
             self.dbapi.commit()
+            self.update_backlinks(family)
         # Emit after added:
         if emit:
             self.emit(emit, ([family.handle],))
 
     def commit_citation(self, citation, trans, change_time=None):
-        ## FIXME: update reference, for back references
         emit = None
         if citation.handle in self.citation_map:
             emit = "citation-update"
@@ -1359,12 +1358,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                         pickle.dumps(citation.serialize())])
         if not trans.batch:
             self.dbapi.commit()
+            self.update_backlinks(citation)
         # Emit after added:
         if emit:
             self.emit(emit, ([citation.handle],))
 
     def commit_source(self, source, trans, change_time=None):
-        ## FIXME: update reference, for back references
         emit = None
         if source.handle in self.source_map:
             emit = "source-update"
@@ -1386,12 +1385,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                         pickle.dumps(source.serialize())])
         if not trans.batch:
             self.dbapi.commit()
+            self.update_backlinks(source)
         # Emit after added:
         if emit:
             self.emit(emit, ([source.handle],))
 
     def commit_repository(self, repository, trans, change_time=None):
-        ## FIXME: update reference, for back references
         emit = None
         if repository.handle in self.repository_map:
             emit = "repository-update"
@@ -1408,12 +1407,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                        [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())])
         if not trans.batch:
             self.dbapi.commit()
+            self.update_backlinks(repository)
         # Emit after added:
         if emit:
             self.emit(emit, ([repository.handle],))
 
     def commit_note(self, note, trans, change_time=None):
-        ## FIXME: update reference, for back references
         emit = None
         if note.handle in self.note_map:
             emit = "note-update"
@@ -1430,12 +1429,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                        [note.handle, note.gramps_id, pickle.dumps(note.serialize())])
         if not trans.batch:
             self.dbapi.commit()
+            self.update_backlinks(note)
         # Emit after added:
         if emit:
             self.emit(emit, ([note.handle],))
 
     def commit_place(self, place, trans, change_time=None):
-        ## FIXME: update reference, for back references
         emit = None
         if place.handle in self.place_map:
             emit = "place-update"
@@ -1457,12 +1456,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                         pickle.dumps(place.serialize())])
         if not trans.batch:
             self.dbapi.commit()
+            self.update_backlinks(place)
         # Emit after added:
         if emit:
             self.emit(emit, ([place.handle],))
 
     def commit_event(self, event, trans, change_time=None):
-        ## FIXME: update reference, for back references
         emit = None
         if event.handle in self.event_map:
             emit = "event-update"
@@ -1479,14 +1478,47 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                        [event.handle, 
                         event.gramps_id, 
                         pickle.dumps(event.serialize())])
+
+        self.update_event_attributes(
+            [str(attr.type) for attr in event.attribute_list
+             if attr.type.is_custom() and str(attr.type)])
+
+        if event.type.is_custom():
+            self.update_event_names(str(event.type))
+
         if not trans.batch:
             self.dbapi.commit()
+            self.update_backlinks(event)
+
         # Emit after added:
         if emit:
             self.emit(emit, ([event.handle],))
 
+    def update_backlinks(self, obj):
+        # First, delete the current references:
+        self.dbapi.execute("DELETE FROM reference where obj_handle = ?;",
+                           [obj.handle])
+        # Now, add the current ones:
+        references = set(obj.get_referenced_handles_recursively())
+        for (ref_class_name, ref_handle) in references:
+            self.dbapi.execute("""INSERT into reference 
+                       (obj_handle, obj_class, ref_handle, ref_class)
+                       VALUES(?, ?, ?, ?);""",
+                               [obj.handle, 
+                                obj.__class__.__name__,
+                                ref_handle, 
+                                ref_class_name])
+        # Will commit later
+
+    def update_event_attributes(self, attr_list):
+        # FIXME
+        pass
+
+    def update_event_names(self, event_type):
+        # FIXME
+        pass
+
     def commit_tag(self, tag, trans, change_time=None):
-        ## FIXME: update reference, for back references
         emit = None
         if tag.handle in self.tag_map:
             emit = "tag-update"
@@ -1505,12 +1537,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                         pickle.dumps(tag.serialize())])
         if not trans.batch:
             self.dbapi.commit()
+            self.update_backlinks(tag)
         # Emit after added:
         if emit:
             self.emit(emit, ([tag.handle],))
 
     def commit_media_object(self, media, trans, change_time=None):
-        ## FIXME: update reference, for back references
         emit = None
         if media.handle in self.media_map:
             emit = "media-update"
@@ -1532,6 +1564,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                         pickle.dumps(media.serialize())])
         if not trans.batch:
             self.dbapi.commit()
+            self.update_backlinks(media)
         # Emit after added:
         if emit:
             self.emit(emit, ([media.handle],))
@@ -2197,7 +2230,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         Commits the items that were queued up during the last gedcom
         import for two step adding.
         """
-        pass
+        self.reindex_reference_map(lambda n: n)
 
     def has_handle_for_person(self, key):
         cur = self.dbapi.execute("select * from person where handle = ?", [key])

From 72cfc3f8264ddae6c5ef151153e6a5d9ca9bbb79 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Mon, 25 May 2015 13:33:04 -0400
Subject: [PATCH 045/105] All metadata functionality now implemented

---
 .../plugins/database/bsddb_support/write.py   |   2 +-
 gramps/plugins/database/dbapi.py              | 491 ++++++++++++------
 gramps/plugins/database/dictionarydb.py       |   2 +-
 gramps/plugins/database/djangodb.py           |   2 +-
 4 files changed, 327 insertions(+), 170 deletions(-)

diff --git a/gramps/plugins/database/bsddb_support/write.py b/gramps/plugins/database/bsddb_support/write.py
index 9630b3ce2..1a3ad039a 100644
--- a/gramps/plugins/database/bsddb_support/write.py
+++ b/gramps/plugins/database/bsddb_support/write.py
@@ -653,7 +653,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
         return False
 
     @catch_db_error
-    def load(self, name, callback, mode=DBMODE_W, force_schema_upgrade=False,
+    def load(self, name, callback=None, mode=DBMODE_W, force_schema_upgrade=False,
              force_bsddb_upgrade=False, force_bsddb_downgrade=False,
              force_python_upgrade=False):
 
diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index d4a2bf09e..aad21039e 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -167,13 +167,34 @@ class Cursor(object):
         pass
 
 class Bookmarks(object):
-    def __init__(self):
-        self.handles = []
+    def __init__(self, default=[]):
+        self.handles = list(default)
+
+    def set(self, handles):
+        self.handles = list(handles)
+
     def get(self):
         return self.handles
+
     def append(self, handle):
         self.handles.append(handle)
 
+    def append_list(self, handles):
+        self.handles += handles
+
+    def remove(self, handle):
+        self.handles.remove(handle)
+
+    def pop(self, item):
+        return self.handles.pop(item)
+
+    def insert(self, pos, item):
+        self.handles.insert(pos, item)
+
+    def close(self):
+        del self.handles
+    
+
 class DBAPITxn(DbTxn):
     def __init__(self, message, db, batch=False):
         DbTxn.__init__(self, message, db, batch)
@@ -386,6 +407,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.readonly = False
         self.db_is_open = True
         self.name_formats = []
+        # Bookmarks:
         self.bookmarks = Bookmarks()
         self.family_bookmarks = Bookmarks()
         self.event_bookmarks = Bookmarks()
@@ -443,7 +465,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.source_map = Map(Table(self._tables["Source"]))
         self.source_id_map = Map(Table(self._tables["Source"]),
                                  keys_func="ids_func",
-                                 contains_func="has_gramps_id_func")
+                                contains_func="has_gramps_id_func")
         self.repository_map  = Map(Table(self._tables["Repository"]))
         self.repository_id_map = Map(Table(self._tables["Repository"]),
                                  keys_func="ids_func",
@@ -478,6 +500,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.abort_possible = False
         self._bm_changes = 0
         self._directory = directory
+        self._has_changed = False
         self.full_name = None
         self.path = None
         self.brief_name = None
@@ -503,10 +526,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def transaction_commit(self, txn):
         self.dbapi.commit()
 
-    def get_undodb(self):
-        ## FIXME
-        return None
-
     def transaction_abort(self, txn):
         self.dbapi.rollback()
 
@@ -748,12 +767,42 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return gid
 
     def get_mediapath(self):
-        cur = self.dbapi.execute("select * from metadata where setting = ?", ["media-path"])
+        return self.get_metadata("media-path", "")
+
+    def set_mediapath(self, mediapath):
+        return self.set_metadata("media-path", mediapath)
+
+    def get_metadata(self, key, default=[]):
+        """
+        Get an item from the database.
+        """
+        cur = self.dbapi.execute("SELECT * FROM metadata WHERE setting = ?;", [key])
         row = cur.fetchone()
         if row:
-            return row["value"]
+            return pickle.loads(row["value"])
+        elif default == []:
+            return []
         else:
-            return None
+            return default
+
+    def set_metadata(self, key, value):
+        """
+        key: string
+        value: item, will be serialized here
+        """
+        cur = self.dbapi.execute("SELECT * FROM metadata WHERE setting = ?;", [key])
+        row = cur.fetchone()
+        if row:
+            cur = self.dbapi.execute("UPDATE metadata SET value = ? WHERE setting = ?;", 
+                                     [pickle.dumps(value), key])
+        else:
+            cur = self.dbapi.execute("INSERT INTO metadata (setting, value) VALUES (?, ?);", 
+                                     [key, pickle.dumps(value)])
+        self.dbapi.commit()
+
+    def set_default_person_handle(self, handle):
+        self.set_metadata("default-person-handle", handle)
+        self.emit('home-person-changed')
 
     def get_name_group_keys(self):
         cur = self.dbapi.execute("select name from name_group order by name;")
@@ -1129,29 +1178,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                  [sname, grouping])
         self.dbapi.commit()
 
-    def set_default_person_handle(self, handle):
-        cur = self.dbapi.execute("select * from metadata where setting = ?", ["default-person"])
-        row = cur.fetchone()
-        if row:
-            cur = self.dbapi.execute("UPDATE metadata SET value = ? WHERE setting = ?;", 
-                                     [handle, "default-person"])
-        else:
-            cur = self.dbapi.execute("INSERT INTO metadata (setting, value) VALUES (?, ?);", 
-                                     ["default-person", handle])
-        self.emit('home-person-changed')
-        self.dbapi.commit()
-
-    def set_mediapath(self, mediapath):
-        cur = self.dbapi.execute("select * from metadata where setting = ?", ["media-path"])
-        row = cur.fetchone()
-        if row:
-            cur = self.dbapi.execute("UPDATE metadata SET value = ? WHERE setting = ?;", 
-                                     [mediapath, "media-path"])
-        else:
-            cur = self.dbapi.execute("INSERT INTO metadata (setting, value) VALUES (?, ?);", 
-                                     ["media-path", mediapath])
-        self.dbapi.commit()
-
     def get_raw_person_data(self, handle):
         if handle in self.person_map:
             return self.person_map[handle]
@@ -1312,6 +1338,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # Emit after added:
         if emit:
             self.emit(emit, ([person.handle],))
+        self._has_changed = True
 
     def commit_family(self, family, trans, change_time=None):
         emit = None
@@ -1335,6 +1362,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # Emit after added:
         if emit:
             self.emit(emit, ([family.handle],))
+        self._has_changed = True
 
     def commit_citation(self, citation, trans, change_time=None):
         emit = None
@@ -1362,6 +1390,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # Emit after added:
         if emit:
             self.emit(emit, ([citation.handle],))
+        self._has_changed = True
 
     def commit_source(self, source, trans, change_time=None):
         emit = None
@@ -1389,6 +1418,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # Emit after added:
         if emit:
             self.emit(emit, ([source.handle],))
+        self._has_changed = True
 
     def commit_repository(self, repository, trans, change_time=None):
         emit = None
@@ -1411,6 +1441,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # Emit after added:
         if emit:
             self.emit(emit, ([repository.handle],))
+        self._has_changed = True
 
     def commit_note(self, note, trans, change_time=None):
         emit = None
@@ -1433,6 +1464,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # Emit after added:
         if emit:
             self.emit(emit, ([note.handle],))
+        self._has_changed = True
 
     def commit_place(self, place, trans, change_time=None):
         emit = None
@@ -1460,6 +1492,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # Emit after added:
         if emit:
             self.emit(emit, ([place.handle],))
+        self._has_changed = True
 
     def commit_event(self, event, trans, change_time=None):
         emit = None
@@ -1493,6 +1526,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # Emit after added:
         if emit:
             self.emit(emit, ([event.handle],))
+        self._has_changed = True
 
     def update_backlinks(self, obj):
         # First, delete the current references:
@@ -1773,12 +1807,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             if not transaction.batch:
                 self.dbapi.commit()
 
-    ## Missing:
-
-    def backup(self):
-        ## FIXME
-        pass
-
     def close(self):
         if self._directory:
             from gramps.plugins.export.exportxml import XmlWriter
@@ -1788,6 +1816,39 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             writer.write(filename)
             filename = os.path.join(self._directory, "meta_data.db")
             touch(filename)
+            # Save metadata
+            self.set_metadata('bookmarks', self.bookmarks.get())
+            self.set_metadata('family_bookmarks', self.family_bookmarks.get())
+            self.set_metadata('event_bookmarks', self.event_bookmarks.get())
+            self.set_metadata('source_bookmarks', self.source_bookmarks.get())
+            self.set_metadata('citation_bookmarks', self.citation_bookmarks.get())
+            self.set_metadata('repo_bookmarks', self.repo_bookmarks.get())
+            self.set_metadata('media_bookmarks', self.media_bookmarks.get())
+            self.set_metadata('place_bookmarks', self.place_bookmarks.get())
+            self.set_metadata('note_bookmarks', self.note_bookmarks.get())
+            
+            # Custom type values, sets
+            self.set_metadata('event_names', self.event_names)
+            self.set_metadata('fattr_names', self.family_attributes)
+            self.set_metadata('pattr_names', self.individual_attributes)
+            self.set_metadata('sattr_names', self.source_attributes)
+            self.set_metadata('marker_names', self.marker_names)
+            self.set_metadata('child_refs', self.child_ref_types)
+            self.set_metadata('family_rels', self.family_rel_types)
+            self.set_metadata('event_roles', self.event_role_names)
+            self.set_metadata('name_types', self.name_types)
+            self.set_metadata('origin_types', self.origin_types)
+            self.set_metadata('repo_types', self.repository_types)
+            self.set_metadata('note_types', self.note_types)
+            self.set_metadata('sm_types', self.source_media_types)
+            self.set_metadata('url_types', self.url_types)
+            self.set_metadata('mattr_names', self.media_attributes)
+            self.set_metadata('eattr_names', self.event_attributes)
+            self.set_metadata('place_types', self.place_types)
+            
+            # surname list
+            self.set_metadata('surname_list', self.surname_list)
+            
             self.dbapi.close()
 
     def find_backlink_handles(self, handle, include_classes=None):
@@ -1815,136 +1876,185 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 yield (row["obj_class"], row["obj_handle"])
 
     def find_initial_person(self):
-        items = self.person_map.keys()
-        if len(items) > 0:
-            return self.get_person_from_handle(list(items)[0])
-        return None
-
-    def find_place_child_handles(self, handle):
-        ## FIXME
-        return []
+        handle = self.get_default_handle()
+        person = None
+        if handle:
+            person = self.get_person_from_handle(handle)
+            if person:
+                return person
+        cur = self.dbapi.execute("SELECT handle FROM person;")
+        row = cur.fetchone()
+        if row:
+            return row[0]
 
     def get_bookmarks(self):
         return self.bookmarks
 
-    def get_child_reference_types(self):
-        ## FIXME
-        return []
-
     def get_citation_bookmarks(self):
         return self.citation_bookmarks
 
     def get_default_handle(self):
-        cur = self.dbapi.execute("select * from metadata where setting = ?", ["default-person"])
-        row = cur.fetchone()
-        if row:
-            return row["value"]
-        else:
-            return None
+        return self.get_metadata("default-person-handle", None)
 
-    def get_event_attribute_types(self):
-        ## FIXME
-        return []
-
-    def get_event_bookmarks(self):
-        return self.event_bookmarks
-
-    def get_event_roles(self):
-        ## FIXME
-        return []
-
-    def get_event_types(self):
-        ## FIXME
-        return []
-
-    def get_family_attribute_types(self):
-        ## FIXME
-        return []
-
-    def get_family_bookmarks(self):
-        return self.family_bookmarks
-
-    def get_family_event_types(self):
-        ## FIXME
-        return []
-
-    def get_family_relation_types(self):
-        ## FIXME
-        return []
-
-    def get_media_attribute_types(self):
-        ## FIXME
-        return []
-
-    def get_media_bookmarks(self):
-        return self.media_bookmarks
-
-    def get_name_types(self):
-        ## FIXME
-        return []
-
-    def get_note_bookmarks(self):
-        return self.note_bookmarks
-
-    def get_note_types(self):
-        ## FIXME
-        return []
-
-    def get_origin_types(self):
-        ## FIXME
-        return []
-
-    def get_person_attribute_types(self):
-        ## FIXME
-        return []
-
-    def get_person_event_types(self):
-        ## FIXME
-        return []
-
-    def get_place_bookmarks(self):
-        return self.place_bookmarks
-
-    def get_place_tree_cursor(self):
-        ## FIXME
-        return []
-
-    def get_place_types(self):
-        ## FIXME
-        return []
-
-    def get_repo_bookmarks(self):
-        return self.repo_bookmarks
-
-    def get_repository_types(self):
-        ## FIXME
-        return []
-
-    def get_save_path(self):
-        return self._directory
-
-    def get_source_attribute_types(self):
-        ## FIXME
-        return []
-
-    def get_source_bookmarks(self):
-        return self.source_bookmarks
-
-    def get_source_media_types(self):
+    def find_place_child_handles(self, handle):
         ## FIXME
         return []
 
     def get_surname_list(self):
-        ## FIXME
-        return []
+        """
+        Return the list of locale-sorted surnames contained in the database.
+        """
+        return self.surname_list
+
+    def get_event_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Event instances
+        in the database.
+        """
+        return list(self.event_attributes)
+
+    def get_event_types(self):
+        """
+        Return a list of all event types in the database.
+        """
+        return list(self.event_names)
+
+    def get_person_event_types(self):
+        """
+        Deprecated:  Use get_event_types
+        """
+        return list(self.event_names)
+
+    def get_person_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Person instances 
+        in the database.
+        """
+        return list(self.individual_attributes)
+
+    def get_family_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Family instances 
+        in the database.
+        """
+        return list(self.family_attributes)
+
+    def get_family_event_types(self):
+        """
+        Deprecated:  Use get_event_types
+        """
+        return list(self.event_names)
+
+    def get_media_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Media and MediaRef 
+        instances in the database.
+        """
+        return list(self.media_attributes)
+
+    def get_family_relation_types(self):
+        """
+        Return a list of all relationship types assocated with Family
+        instances in the database.
+        """
+        return list(self.family_rel_types)
+
+    def get_child_reference_types(self):
+        """
+        Return a list of all child reference types assocated with Family
+        instances in the database.
+        """
+        return list(self.child_ref_types)
+
+    def get_event_roles(self):
+        """
+        Return a list of all custom event role names assocated with Event
+        instances in the database.
+        """
+        return list(self.event_role_names)
+
+    def get_name_types(self):
+        """
+        Return a list of all custom names types assocated with Person
+        instances in the database.
+        """
+        return list(self.name_types)
+
+    def get_origin_types(self):
+        """
+        Return a list of all custom origin types assocated with Person/Surname
+        instances in the database.
+        """
+        return list(self.origin_types)
+
+    def get_repository_types(self):
+        """
+        Return a list of all custom repository types assocated with Repository 
+        instances in the database.
+        """
+        return list(self.repository_types)
+
+    def get_note_types(self):
+        """
+        Return a list of all custom note types assocated with Note instances 
+        in the database.
+        """
+        return list(self.note_types)
+
+    def get_source_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Source/Citation
+        instances in the database.
+        """
+        return list(self.source_attributes)
+
+    def get_source_media_types(self):
+        """
+        Return a list of all custom source media types assocated with Source 
+        instances in the database.
+        """
+        return list(self.source_media_types)
 
     def get_url_types(self):
-        ## FIXME
-        return []
+        """
+        Return a list of all custom names types assocated with Url instances 
+        in the database.
+        """
+        return list(self.url_types)
+
+    def get_place_types(self):
+        """
+        Return a list of all custom place types assocated with Place instances
+        in the database.
+        """
+        return list(self.place_types)
+
+    def get_event_bookmarks(self):
+        return self.event_bookmarks
+
+    def get_family_bookmarks(self):
+        return self.family_bookmarks
+
+    def get_media_bookmarks(self):
+        return self.media_bookmarks
+
+    def get_note_bookmarks(self):
+        return self.note_bookmarks
+
+    def get_place_bookmarks(self):
+        return self.place_bookmarks
+
+    def get_repo_bookmarks(self):
+        return self.repo_bookmarks
+
+    def get_save_path(self):
+        return self._directory
+
+    def get_source_bookmarks(self):
+        return self.source_bookmarks
 
     def has_changed(self):
-        ## FIXME
-        return True
+        return self._has_changed
 
     def is_open(self):
         return self._directory is not None
@@ -1994,7 +2104,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def iter_tags(self):
         return (Tag.create(data[1]) for data in self.get_tag_cursor())
 
-    def load(self, directory, pulse_progress=None, mode=None, 
+    def load(self, directory, callback=None, mode=None, 
              force_schema_upgrade=False, 
              force_bsddb_upgrade=False, 
              force_bsddb_downgrade=False, 
@@ -2102,19 +2212,50 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
                                   ref_handle ON reference (ref_handle);
         """)
+        # Load metadata
+        self.bookmarks.set(self.get_metadata('bookmarks'))
+        self.family_bookmarks.set(self.get_metadata('family_bookmarks'))
+        self.event_bookmarks.set(self.get_metadata('event_bookmarks'))
+        self.source_bookmarks.set(self.get_metadata('source_bookmarks'))
+        self.citation_bookmarks.set(self.get_metadata('citation_bookmarks'))
+        self.repo_bookmarks.set(self.get_metadata('repo_bookmarks'))
+        self.media_bookmarks.set(self.get_metadata('media_bookmarks'))
+        self.place_bookmarks.set(self.get_metadata('place_bookmarks'))
+        self.note_bookmarks.set(self.get_metadata('note_bookmarks'))
 
-    def redo(self, update_history=True):
-        ## FIXME
-        pass
-
-    def restore(self):
-        ## FIXME
-        pass
-
+        # Custom type values
+        self.event_names = self.get_metadata('event_names', set())
+        self.family_attributes = self.get_metadata('fattr_names', set())
+        self.individual_attributes = self.get_metadata('pattr_names', set())
+        self.source_attributes = self.get_metadata('sattr_names', set())
+        self.marker_names = self.get_metadata('marker_names', set())
+        self.child_ref_types = self.get_metadata('child_refs', set())
+        self.family_rel_types = self.get_metadata('family_rels', set())
+        self.event_role_names = self.get_metadata('event_roles', set())
+        self.name_types = self.get_metadata('name_types', set())
+        self.origin_types = self.get_metadata('origin_types', set())
+        self.repository_types = self.get_metadata('repo_types', set())
+        self.note_types = self.get_metadata('note_types', set())
+        self.source_media_types = self.get_metadata('sm_types', set())
+        self.url_types = self.get_metadata('url_types', set())
+        self.media_attributes = self.get_metadata('mattr_names', set())
+        self.event_attributes = self.get_metadata('eattr_names', set())
+        self.place_types = self.get_metadata('place_types', set())
+        
+        # surname list
+        self.surname_list = self.get_metadata('surname_list')
+        
     def set_prefixes(self, person, media, family, source, citation, 
                      place, event, repository, note):
-        ## FIXME
-        pass
+        self.set_person_id_prefix(person)
+        self.set_object_id_prefix(media)
+        self.set_family_id_prefix(family)
+        self.set_source_id_prefix(source)
+        self.set_citation_id_prefix(citation)
+        self.set_place_id_prefix(place)
+        self.set_event_id_prefix(event)
+        self.set_repository_id_prefix(repository)
+        self.set_note_id_prefix(note)
 
     def set_save_path(self, directory):
         self._directory = directory
@@ -2122,10 +2263,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.path = self.full_name
         self.brief_name = os.path.basename(self._directory)
 
-    def undo(self, update_history=True):
-        ## FIXME
-        pass
-
     def write_version(self, directory):
         """Write files for a newly created DB."""
         versionpath = os.path.join(directory, str(DBBACKEND))
@@ -2438,3 +2575,23 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def _order_by_tag_key(self, tag):
         return glocale.sort_key(tag.get_name())
 
+    def backup(self):
+        ## FIXME
+        pass
+
+    def restore(self):
+        ## FIXME
+        pass
+
+    def get_undodb(self):
+        ## FIXME
+        return None
+
+    def undo(self, update_history=True):
+        ## FIXME
+        pass
+
+    def redo(self, update_history=True):
+        ## FIXME
+        pass
+
diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 8b8d8beb7..3fe7e0da5 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -1725,7 +1725,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def iter_tags(self):
         return (Tag.create(key) for key in self.tag_map.values())
 
-    def load(self, directory, pulse_progress=None, mode=None, 
+    def load(self, directory, callback=None, mode=None, 
              force_schema_upgrade=False, 
              force_bsddb_upgrade=False, 
              force_bsddb_downgrade=False, 
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index 0091d033a..a7f64f13e 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -360,7 +360,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if directory:
             self.load(directory)
 
-    def load(self, directory, pulse_progress=None, mode=None, 
+    def load(self, directory, callback=None, mode=None, 
              force_schema_upgrade=False,
              force_bsddb_upgrade=False,
              force_bsddb_downgrade=False,

From 5e96f8a72e610b3800d5af1f1da4f437f65f7342 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Mon, 25 May 2015 17:53:14 -0400
Subject: [PATCH 046/105] DB-API: added undo-redo infrastructure

---
 gramps/gen/db/__init__.py        |   1 +
 gramps/gen/db/undoredo.py        |  73 +++++++++++++++++-----
 gramps/plugins/database/dbapi.py | 101 +++++++++++++++++++++++++------
 3 files changed, 141 insertions(+), 34 deletions(-)

diff --git a/gramps/gen/db/__init__.py b/gramps/gen/db/__init__.py
index 10f751303..d9fcf1ad1 100644
--- a/gramps/gen/db/__init__.py
+++ b/gramps/gen/db/__init__.py
@@ -88,6 +88,7 @@ from .base import *
 from .dbconst import *
 from .txn import *
 from .exceptions import *
+from .undoredo import *
 
 def find_surname_name(key, data):
     """
diff --git a/gramps/gen/db/undoredo.py b/gramps/gen/db/undoredo.py
index b12735093..bfcd652d6 100644
--- a/gramps/gen/db/undoredo.py
+++ b/gramps/gen/db/undoredo.py
@@ -1,4 +1,10 @@
+#-------------------------------------------------------------------------
+#
+# Standard python modules
+#
+#-------------------------------------------------------------------------
 import time
+import pickle
 from collections import deque
 
 class DbUndo(object):
@@ -19,6 +25,20 @@ class DbUndo(object):
         self.redoq = deque()
         self.undo_history_timestamp = time.time()
         self.txn = None
+        # N.B. the databases have to be in the same order as the numbers in
+        # xxx_KEY in gen/db/dbconst.py
+        self.mapbase = (
+                        self.db.person_map,
+                        self.db.family_map,
+                        self.db.source_map,
+                        self.db.event_map,
+                        self.db.media_map,
+                        self.db.place_map,
+                        self.db.repository_map,
+                        self.db.note_map,
+                        self.db.tag_map,
+                        self.db.citation_map,
+                        )
 
     def clear(self):
         """
@@ -85,6 +105,16 @@ class DbUndo(object):
         """         
         raise NotImplementedError
 
+    def __redo(self, update_history):
+        """
+        """
+        raise NotImplementedError
+
+    def __undo(self, update_history):
+        """
+        """
+        raise NotImplementedError
+
     def commit(self, txn, msg):
         """
         Commit the transaction to the undo/redo database.  "txn" should be
@@ -110,27 +140,40 @@ class DbUndo(object):
             return False
         return self.__redo(update_history)
 
-    def undoredo(func):
+    def undo_reference(self, data, handle, db_map):
         """
-        Decorator function to wrap undo and redo operations within a bsddb
-        transaction.  It also catches bsddb errors and raises an exception
-        as appropriate
+        Helper method to undo a reference map entry
         """
-        pass
+        try:
+            if data is None:
+                db_map.delete(handle, txn=self.txn)
+            else:
+                db_map.put(handle, data, txn=self.txn)
 
-    def __redo(self, update_history=True):
-        """
-        Access the last undone transaction, and revert the data to the state 
-        before the transaction was undone.
-        """
-        pass
+        except DBERRS as msg:
+            self.db._log_error()
+            raise DbError(msg)
 
-    def __undo(self, db=None, update_history=True):
+    def undo_data(self, data, handle, db_map, emit, signal_root):
         """
-        Access the last committed transaction, and revert the data to the 
-        state before the transaction was committed.
+        Helper method to undo/redo the changes made
         """
-        pass
+        try:
+            if data is None:
+                emit(signal_root + '-delete', ([handle2internal(handle)],))
+                db_map.delete(handle, txn=self.txn)
+            else:
+                ex_data = db_map.get(handle, txn=self.txn)
+                if ex_data:
+                    signal = signal_root + '-update'
+                else:
+                    signal = signal_root + '-add'
+                db_map.put(handle, data, txn=self.txn)
+                emit(signal, ([handle2internal(handle)],))
+
+        except DBERRS as msg:
+            self.db._log_error()
+            raise DbError(msg)
 
     undo_count = property(lambda self:len(self.undoq))
     redo_count = property(lambda self:len(self.redoq))
diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index aad21039e..9df140e32 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -19,12 +19,11 @@ import shutil
 import gramps
 from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
-from gramps.gen.db import (DbReadBase, DbWriteBase, DbTxn, 
+from gramps.gen.db import (DbReadBase, DbWriteBase, DbTxn, DbUndo,
                            KEY_TO_NAME_MAP, KEY_TO_CLASS_MAP, 
                            CLASS_TO_KEY_MAP)
 from gramps.gen.utils.callback import Callback
 from gramps.gen.updatecallback import UpdateCallback
-from gramps.gen.db.undoredo import DbUndo
 from gramps.gen.db.dbconst import *
 from gramps.gen.db import (PERSON_KEY,
                            FAMILY_KEY,
@@ -52,6 +51,71 @@ def touch(fname, mode=0o666, dir_fd=None, **kwargs):
         os.utime(f.fileno() if os.utime in os.supports_fd else fname,
                  dir_fd=None if os.supports_fd else dir_fd, **kwargs)
 
+class DBAPIUndo(DbUndo):
+    def __init__(self, grampsdb, path):
+        super(DBAPIUndo, self).__init__(grampsdb)
+        self.undodb = grampsdb
+        self.path = path
+
+    def open(self, value=None):
+        """
+        Open the backing storage.  Needs to be overridden in the derived
+        class.
+        """
+        pass
+        # FIXME
+
+    def close(self):
+        """
+        Close the backing storage.  Needs to be overridden in the derived
+        class.
+        """        
+        pass
+        # FIXME
+
+    def append(self, value):
+        """
+        Add a new entry on the end.  Needs to be overridden in the derived
+        class.
+        """        
+        pass
+        # FIXME
+
+    def __getitem__(self, index):
+        """
+        Returns an entry by index number.  Needs to be overridden in the
+        derived class.
+        """        
+        return None
+        # FIXME
+
+    def __setitem__(self, index, value):
+        """
+        Set an entry to a value.  Needs to be overridden in the derived class.
+        """           
+        pass
+        # FIXME
+
+    def __len__(self):
+        """
+        Returns the number of entries.  Needs to be overridden in the derived
+        class.
+        """         
+        return 0
+        # FIXME
+
+    def __redo(self, update_history):
+        """
+        """
+        pass
+        # FIXME
+
+    def __undo(self, update_history):
+        """
+        """
+        pass
+        # FIXME
+
 class Environment(object):
     """
     Implements the Environment API.
@@ -427,6 +491,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.set_repository_id_prefix('R%04d')
         self.set_note_id_prefix('N%04d')
         # ----------------------------------
+        self.undodb = None
         self.id_trans  = DBAPITxn("ID Transaction", self)
         self.fid_trans = DBAPITxn("FID Transaction", self)
         self.pid_trans = DBAPITxn("PID Transaction", self)
@@ -484,19 +549,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                  contains_func="has_gramps_id_func")
         self.tag_map  = Map(Table(self._tables["Tag"]))
         self.metadata   = Map(Table({"cursor_func": lambda: MetaCursor()}))
-        ## FIXME: add appropriate methods:
-        self.name_group = Map(Table({"handles_func": self.get_name_group_keys,  # keys
-                                     "has_handle_func": self.has_name_group_key, # key in table 
-                                     "cursor_func": None, # create a cursor, values
-                                     "add_func": self.set_name_group_mapping, # add a key, value
-                                     "count_func": None})) # how many items?
         self.undo_callback = None
         self.redo_callback = None
         self.undo_history_callback = None
         self.modified   = 0
         self.txn = DBAPITxn("DBAPI Transaction", self)
         self.transaction = None
-        self.undodb = DbUndo(self)
         self.abort_possible = False
         self._bm_changes = 0
         self._directory = directory
@@ -984,7 +1042,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return (handle for handle in self.family_map.keys())
 
     def get_tag_from_name(self, name):
-        ## Slow, but typically not too many tags:
+        ## FIXME: Slow, but typically not too many tags:
         for data in self.tag_map.values():
             tag = Tag.create(data)
             if tag.name == name:
@@ -2244,6 +2302,11 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         
         # surname list
         self.surname_list = self.get_metadata('surname_list')
+
+        self._directory = directory
+        self.undolog = os.path.join(self._directory, DBUNDOFN)
+        self.undodb = DBAPIUndo(self, self.undolog)
+        self.undodb.open()
         
     def set_prefixes(self, person, media, family, source, citation, 
                      place, event, repository, note):
@@ -2576,22 +2639,22 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return glocale.sort_key(tag.get_name())
 
     def backup(self):
-        ## FIXME
+        """
+        If you wish to support an optional backup routine, put it here.
+        """
         pass
 
     def restore(self):
-        ## FIXME
+        """
+        If you wish to support an optional restore routine, put it here.
+        """
         pass
 
     def get_undodb(self):
-        ## FIXME
-        return None
+        return self.undodb
 
     def undo(self, update_history=True):
-        ## FIXME
-        pass
+        return self.undodb.undo(update_history)
 
     def redo(self, update_history=True):
-        ## FIXME
-        pass
-
+        return self.undodb.redo(update_history)

From 986022cee155f4618736746066b2e692b0ec3f33 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Mon, 25 May 2015 19:04:27 -0400
Subject: [PATCH 047/105] DB-API: committing objects updates secondary items

---
 gramps/plugins/database/dbapi.py | 173 ++++++++++++++++++++++++++++---
 1 file changed, 160 insertions(+), 13 deletions(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index 9df140e32..c8673104f 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -10,6 +10,7 @@ import re
 import os
 import logging
 import shutil
+import bisect
 
 #------------------------------------------------------------------------
 #
@@ -1374,6 +1375,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         emit = None
         if person.handle in self.person_map:
             emit = "person-update"
+            old_person = self.get_person_from_handle(person.handle)
+            # Update gender statistics if necessary
+            if (old_person.gender != person.gender or
+                old_person.primary_name.first_name !=
+                  person.primary_name.first_name):
+
+                self.genderStats.uncount_person(old_person)
+                self.genderStats.count_person(person)
+            # Update surname list if necessary
+            if (self._order_by_person_key(person) != 
+                self._order_by_person_key(old_person)):
+                self.remove_from_surname_list(old_person)
+                self.add_to_surname_list(person, trans.batch)
+            # update the person:
             self.dbapi.execute("""UPDATE person SET gramps_id = ?, 
                                                     order_by = ?,
                                                     blob = ? 
@@ -1384,6 +1399,9 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 person.handle])
         else:
             emit = "person-add"
+            self.genderStats.count_person(person)
+            self.add_to_surname_list(person, trans.batch)
+            # Insert the person:
             self.dbapi.execute("""insert into person (handle, order_by, gramps_id, blob)
                             values(?, ?, ?, ?);""", 
                                [person.handle, 
@@ -1393,11 +1411,68 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not trans.batch:
             self.dbapi.commit()
             self.update_backlinks(person)
+        # Other misc update tasks:
+        self.individual_attributes.update(
+            [str(attr.type) for attr in person.attribute_list
+             if attr.type.is_custom() and str(attr.type)])
+
+        self.event_role_names.update([str(eref.role)
+                                      for eref in person.event_ref_list
+                                      if eref.role.is_custom()])
+
+        self.name_types.update([str(name.type)
+                                for name in ([person.primary_name]
+                                             + person.alternate_names)
+                                if name.type.is_custom()])
+        all_surn = []  # new list we will use for storage
+        all_surn += person.primary_name.get_surname_list() 
+        for asurname in person.alternate_names:
+            all_surn += asurname.get_surname_list()
+        self.origin_types.update([str(surn.origintype) for surn in all_surn
+                                if surn.origintype.is_custom()])
+        all_surn = None
+        self.url_types.update([str(url.type) for url in person.urls
+                               if url.type.is_custom()])
+        attr_list = []
+        for mref in person.media_list:
+            attr_list += [str(attr.type) for attr in mref.attribute_list
+                          if attr.type.is_custom() and str(attr.type)]
+        self.media_attributes.update(attr_list)
         # Emit after added:
         if emit:
             self.emit(emit, ([person.handle],))
         self._has_changed = True
 
+    def add_to_surname_list(self, person, batch_transaction):
+        """
+        Add surname to surname list
+        """
+        if batch_transaction:
+            return
+        name = conv_to_unicode(self._order_by_person_key(person), 'utf-8')
+        i = bisect.bisect(self.surname_list, name)
+        if 0 < i <= len(self.surname_list):
+            if self.surname_list[i-1] != name:
+                self.surname_list.insert(i, name)
+        else:
+            self.surname_list.insert(i, name)
+
+    def remove_from_surname_list(self, person):
+        """
+        Check whether there are persons with the same surname left in
+        the database. 
+        
+        If not then we need to remove the name from the list.
+        The function must be overridden in the derived class.
+        """
+        name = self._order_by_person_key(person)
+        if isinstance(name, str):
+            uname = name
+            name = name.encode('utf-8')
+        else:
+            uname = str(name)
+        # FIXME: check database
+
     def commit_family(self, family, trans, change_time=None):
         emit = None
         if family.handle in self.family_map:
@@ -1417,6 +1492,31 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not trans.batch:
             self.dbapi.commit()
             self.update_backlinks(family)
+        # Misc updates:
+        self.family_attributes.update(
+            [str(attr.type) for attr in family.attribute_list
+             if attr.type.is_custom() and str(attr.type)])
+
+        rel_list = []
+        for ref in family.child_ref_list:
+            if ref.frel.is_custom():
+                rel_list.append(str(ref.frel))
+            if ref.mrel.is_custom():
+                rel_list.append(str(ref.mrel))
+        self.child_ref_types.update(rel_list)
+
+        self.event_role_names.update(
+            [str(eref.role) for eref in family.event_ref_list
+             if eref.role.is_custom()])
+
+        if family.type.is_custom():
+            self.family_rel_types.add(str(family.type))
+
+        attr_list = []
+        for mref in family.media_list:
+            attr_list += [str(attr.type) for attr in mref.attribute_list
+                          if attr.type.is_custom() and str(attr.type)]
+        self.media_attributes.update(attr_list)
         # Emit after added:
         if emit:
             self.emit(emit, ([family.handle],))
@@ -1445,6 +1545,17 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not trans.batch:
             self.dbapi.commit()
             self.update_backlinks(citation)
+        # Misc updates:
+        attr_list = []
+        for mref in citation.media_list:
+            attr_list += [str(attr.type) for attr in mref.attribute_list
+                          if attr.type.is_custom() and str(attr.type)]
+        self.media_attributes.update(attr_list)
+
+        self.source_attributes.update(
+            [str(attr.type) for attr in citation.attribute_list
+             if attr.type.is_custom() and str(attr.type)])
+
         # Emit after added:
         if emit:
             self.emit(emit, ([citation.handle],))
@@ -1473,6 +1584,19 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not trans.batch:
             self.dbapi.commit()
             self.update_backlinks(source)
+        # Misc updates:
+        self.source_media_types.update(
+            [str(ref.media_type) for ref in source.reporef_list
+             if ref.media_type.is_custom()])       
+
+        attr_list = []
+        for mref in source.media_list:
+            attr_list += [str(attr.type) for attr in mref.attribute_list
+                          if attr.type.is_custom() and str(attr.type)]
+        self.media_attributes.update(attr_list)
+        self.source_attributes.update(
+            [str(attr.type) for attr in source.attribute_list
+             if attr.type.is_custom() and str(attr.type)])
         # Emit after added:
         if emit:
             self.emit(emit, ([source.handle],))
@@ -1496,6 +1620,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not trans.batch:
             self.dbapi.commit()
             self.update_backlinks(repository)
+        # Misc updates:
+        if repository.type.is_custom():
+            self.repository_types.add(str(repository.type))
+
+        self.url_types.update([str(url.type) for url in repository.urls
+                               if url.type.is_custom()])
         # Emit after added:
         if emit:
             self.emit(emit, ([repository.handle],))
@@ -1519,6 +1649,9 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not trans.batch:
             self.dbapi.commit()
             self.update_backlinks(note)
+        # Misc updates:
+        if note.type.is_custom():
+            self.note_types.add(str(note.type))        
         # Emit after added:
         if emit:
             self.emit(emit, ([note.handle],))
@@ -1547,6 +1680,18 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not trans.batch:
             self.dbapi.commit()
             self.update_backlinks(place)
+        # Misc updates:
+        if place.get_type().is_custom():
+            self.place_types.add(str(place.get_type()))
+
+        self.url_types.update([str(url.type) for url in place.urls
+                               if url.type.is_custom()])
+
+        attr_list = []
+        for mref in place.media_list:
+            attr_list += [str(attr.type) for attr in mref.attribute_list
+                          if attr.type.is_custom() and str(attr.type)]
+        self.media_attributes.update(attr_list)
         # Emit after added:
         if emit:
             self.emit(emit, ([place.handle],))
@@ -1569,18 +1714,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                        [event.handle, 
                         event.gramps_id, 
                         pickle.dumps(event.serialize())])
-
-        self.update_event_attributes(
-            [str(attr.type) for attr in event.attribute_list
-             if attr.type.is_custom() and str(attr.type)])
-
-        if event.type.is_custom():
-            self.update_event_names(str(event.type))
-
         if not trans.batch:
             self.dbapi.commit()
             self.update_backlinks(event)
-
+        # Misc updates:
+        self.event_attributes.update(
+            [str(attr.type) for attr in event.attribute_list
+             if attr.type.is_custom() and str(attr.type)])
+        if event.type.is_custom():
+            self.event_names.add(str(event.type))
+        attr_list = []
+        for mref in event.media_list:
+            attr_list += [str(attr.type) for attr in mref.attribute_list
+                          if attr.type.is_custom() and str(attr.type)]
+        self.media_attributes.update(attr_list)
         # Emit after added:
         if emit:
             self.emit(emit, ([event.handle],))
@@ -1657,6 +1804,10 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not trans.batch:
             self.dbapi.commit()
             self.update_backlinks(media)
+        # Misc updates:
+        self.media_attributes.update(
+            [str(attr.type) for attr in media.attribute_list
+             if attr.type.is_custom() and str(attr.type)])
         # Emit after added:
         if emit:
             self.emit(emit, ([media.handle],))
@@ -1954,10 +2105,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def get_default_handle(self):
         return self.get_metadata("default-person-handle", None)
 
-    def find_place_child_handles(self, handle):
-        ## FIXME
-        return []
-
     def get_surname_list(self):
         """
         Return the list of locale-sorted surnames contained in the database.

From 54cc6cbff3bb5c3302211a9d9757174934992b08 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Mon, 25 May 2015 21:06:44 -0400
Subject: [PATCH 048/105] DB-API: surname_list not working; added tag map
 support

---
 gramps/plugins/database/dbapi.py | 31 ++++++++++++++++++-------------
 1 file changed, 18 insertions(+), 13 deletions(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index c8673104f..36e4e6a51 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -10,7 +10,6 @@ import re
 import os
 import logging
 import shutil
-import bisect
 
 #------------------------------------------------------------------------
 #
@@ -463,8 +462,10 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 "handles_func": self.get_tag_handles,
                 "add_func": self.add_tag,
                 "commit_func": self.commit_tag,
+                "has_handle_func": self.has_handle_for_tag,
                 "iter_func": self.iter_tags,
                 "count": self.get_number_of_tags,
+                "raw_func": self._get_raw_tag_data,
             })
         # skip GEDCOM cross-ref check for now:
         self.set_feature("skip-check-xref", True)
@@ -1449,13 +1450,13 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         """
         if batch_transaction:
             return
-        name = conv_to_unicode(self._order_by_person_key(person), 'utf-8')
-        i = bisect.bisect(self.surname_list, name)
-        if 0 < i <= len(self.surname_list):
-            if self.surname_list[i-1] != name:
-                self.surname_list.insert(i, name)
-        else:
-            self.surname_list.insert(i, name)
+        #name = self._order_by_person_key(person)
+        #i = bisect.bisect(self.surname_list, name)
+        #if 0 < i <= len(self.surname_list):
+        #    if self.surname_list[i-1] != name:
+        #        self.surname_list.insert(i, name)
+        #else:
+        #    self.surname_list.insert(i, name)
 
     def remove_from_surname_list(self, person):
         """
@@ -1466,11 +1467,11 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         The function must be overridden in the derived class.
         """
         name = self._order_by_person_key(person)
-        if isinstance(name, str):
-            uname = name
-            name = name.encode('utf-8')
-        else:
-            uname = str(name)
+        #if isinstance(name, str):
+        #    uname = name
+        #    name = name.encode('utf-8')
+        #else:
+        #    uname = str(name)
         # FIXME: check database
 
     def commit_family(self, family, trans, change_time=None):
@@ -2615,6 +2616,10 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         cur = self.dbapi.execute("select * from note where handle = ?", [key])
         return cur.fetchone() != None
 
+    def has_handle_for_tag(self, key):
+        cur = self.dbapi.execute("select * from tag where handle = ?", [key])
+        return cur.fetchone() != None
+
     def has_gramps_id_for_person(self, key):
         cur = self.dbapi.execute("select * from person where gramps_id = ?", [key])
         return cur.fetchone() != None

From 3cbc012f6fd74ae3b6c1986a6d1b1ce719d8668c Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Mon, 25 May 2015 22:31:36 -0400
Subject: [PATCH 049/105] DB-API: sql clean up; some FIXME's still left

---
 gramps/plugins/database/dbapi.py | 286 +++++++++++++++++--------------
 1 file changed, 154 insertions(+), 132 deletions(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index 36e4e6a51..51a409240 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -10,6 +10,7 @@ import re
 import os
 import logging
 import shutil
+import bisect
 
 #------------------------------------------------------------------------
 #
@@ -865,12 +866,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.emit('home-person-changed')
 
     def get_name_group_keys(self):
-        cur = self.dbapi.execute("select name from name_group order by name;")
+        cur = self.dbapi.execute("SELECT name FROM name_group ORDER BY name;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_name_group_mapping(self, key):
-        cur = self.dbapi.execute("select grouping from name_group where name = ?;", 
+        cur = self.dbapi.execute("SELECT grouping FROM name_group WHERE name = ?;", 
                                  [key])
         row = cur.fetchone()
         if row:
@@ -885,62 +886,62 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return [row[0] for row in rows]
 
     def get_family_handles(self):
-        cur = self.dbapi.execute("select handle from family;")
+        cur = self.dbapi.execute("SELECT handle FROM family;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_event_handles(self):
-        cur = self.dbapi.execute("select handle from event;")
+        cur = self.dbapi.execute("SELECT handle FROM event;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_citation_handles(self, sort_handles=False):
         if sort_handles:
-            cur = self.dbapi.execute("select handle from citation ORDER BY order_by;")
+            cur = self.dbapi.execute("SELECT handle FROM citation ORDER BY order_by;")
         else:
-            cur = self.dbapi.execute("select handle from citation;")
+            cur = self.dbapi.execute("SELECT handle FROM citation;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_source_handles(self, sort_handles=False):
         if sort_handles:
-            cur = self.dbapi.execute("select handle from source ORDER BY order_by;")
+            cur = self.dbapi.execute("SELECT handle FROM source ORDER BY order_by;")
         else:
-            cur = self.dbapi.execute("select handle from source;")
+            cur = self.dbapi.execute("SELECT handle from source;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_place_handles(self, sort_handles=False):
         if sort_handles:
-            cur = self.dbapi.execute("select handle from place ORDER BY order_by;")
+            cur = self.dbapi.execute("SELECT handle FROM place ORDER BY order_by;")
         else:
-            cur = self.dbapi.execute("select handle from place;")
+            cur = self.dbapi.execute("SELECT handle FROM place;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_repository_handles(self):
-        cur = self.dbapi.execute("select handle from repository;")
+        cur = self.dbapi.execute("SELECT handle FROM repository;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_media_object_handles(self, sort_handles=False):
         if sort_handles:
-            cur = self.dbapi.execute("select handle from media ORDER BY order_by;")
+            cur = self.dbapi.execute("SELECT handle FROM media ORDER BY order_by;")
         else:
-            cur = self.dbapi.execute("select handle from media;")
+            cur = self.dbapi.execute("SELECT handle FROM media;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_note_handles(self):
-        cur = self.dbapi.execute("select handle from note;")
+        cur = self.dbapi.execute("SELECT handle FROM note;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_tag_handles(self, sort_handles=False):
         if sort_handles:
-            cur = self.dbapi.execute("select handle from tag ORDER BY order_by;")
+            cur = self.dbapi.execute("SELECT handle FROM tag ORDER BY order_by;")
         else:
-            cur = self.dbapi.execute("select handle from tag;")
+            cur = self.dbapi.execute("SELECT handle FROM tag;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
@@ -1097,52 +1098,52 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return None
 
     def get_number_of_people(self):
-        cur = self.dbapi.execute("select count(handle) from person;")
+        cur = self.dbapi.execute("SELECT count(handle) FROM person;")
         row = cur.fetchone()
         return row[0]
 
     def get_number_of_events(self):
-        cur = self.dbapi.execute("select count(handle) from event;")
+        cur = self.dbapi.execute("SELECT count(handle) FROM event;")
         row = cur.fetchone()
         return row[0]
 
     def get_number_of_places(self):
-        cur = self.dbapi.execute("select count(handle) from place;")
+        cur = self.dbapi.execute("SELECT count(handle) FROM place;")
         row = cur.fetchone()
         return row[0]
 
     def get_number_of_tags(self):
-        cur = self.dbapi.execute("select count(handle) from tag;")
+        cur = self.dbapi.execute("SELECT count(handle) FROM tag;")
         row = cur.fetchone()
         return row[0]
 
     def get_number_of_families(self):
-        cur = self.dbapi.execute("select count(handle) from family;")
+        cur = self.dbapi.execute("SELECT count(handle) FROM family;")
         row = cur.fetchone()
         return row[0]
 
     def get_number_of_notes(self):
-        cur = self.dbapi.execute("select count(handle) from note;")
+        cur = self.dbapi.execute("SELECT count(handle) FROM note;")
         row = cur.fetchone()
         return row[0]
 
     def get_number_of_citations(self):
-        cur = self.dbapi.execute("select count(handle) from citation;")
+        cur = self.dbapi.execute("SELECT count(handle) FROM citation;")
         row = cur.fetchone()
         return row[0]
 
     def get_number_of_sources(self):
-        cur = self.dbapi.execute("select count(handle) from source;")
+        cur = self.dbapi.execute("SELECT count(handle) FROM source;")
         row = cur.fetchone()
         return row[0]
 
     def get_number_of_media_objects(self):
-        cur = self.dbapi.execute("select count(handle) from media;")
+        cur = self.dbapi.execute("SELECT count(handle) FROM media;")
         row = cur.fetchone()
         return row[0]
 
     def get_number_of_repositories(self):
-        cur = self.dbapi.execute("select count(handle) from repository;")
+        cur = self.dbapi.execute("SELECT count(handle) FROM repository;")
         row = cur.fetchone()
         return row[0]
 
@@ -1221,20 +1222,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return handle in self.media_map
 
     def has_name_group_key(self, key):
-        cur = self.dbapi.execute("select grouping from name_group where name = ?;", 
+        cur = self.dbapi.execute("SELECT grouping FROM name_group WHERE name = ?;", 
                                  [key])
         row = cur.fetchone()
         return True if row else False
 
     def set_name_group_mapping(self, name, grouping):
         sname = name.encode("utf-8")
-        cur = self.dbapi.execute("SELECT * from name_group where name = ?;", 
+        cur = self.dbapi.execute("SELECT * FROM name_group WHERE name = ?;", 
                                  [sname])
         row = cur.fetchone()
         if row:
-            cur = self.dbapi.execute("DELETE from name_group where name = ?;", 
+            cur = self.dbapi.execute("DELETE FROM name_group WHERE name = ?;", 
                                      [sname])
-        cur = self.dbapi.execute("INSERT into name_group (name, grouping) VALUES(?, ?);",
+        cur = self.dbapi.execute("INSERT INTO name_group (name, grouping) VALUES(?, ?);",
                                  [sname, grouping])
         self.dbapi.commit()
 
@@ -1403,15 +1404,15 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.genderStats.count_person(person)
             self.add_to_surname_list(person, trans.batch)
             # Insert the person:
-            self.dbapi.execute("""insert into person (handle, order_by, gramps_id, blob)
-                            values(?, ?, ?, ?);""", 
+            self.dbapi.execute("""INSERT INTO person (handle, order_by, gramps_id, blob)
+                            VALUES(?, ?, ?, ?);""", 
                                [person.handle, 
                                 self._order_by_person_key(person),
                                 person.gramps_id, 
                                 pickle.dumps(person.serialize())])
         if not trans.batch:
-            self.dbapi.commit()
             self.update_backlinks(person)
+            self.dbapi.commit()
         # Other misc update tasks:
         self.individual_attributes.update(
             [str(attr.type) for attr in person.attribute_list
@@ -1450,13 +1451,21 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         """
         if batch_transaction:
             return
-        #name = self._order_by_person_key(person)
-        #i = bisect.bisect(self.surname_list, name)
-        #if 0 < i <= len(self.surname_list):
-        #    if self.surname_list[i-1] != name:
-        #        self.surname_list.insert(i, name)
-        #else:
-        #    self.surname_list.insert(i, name)
+        # TODO: check to see if this is correct
+        name = None
+        primary_name = person.get_primary_name()
+        if primary_name:
+            surname_list = primary_name.get_surname_list()
+            if len(surname_list) > 0:
+                name = surname_list[0].surname
+        if name is None:
+            return
+        i = bisect.bisect(self.surname_list, name)
+        if 0 < i <= len(self.surname_list):
+            if self.surname_list[i-1] != name:
+                self.surname_list.insert(i, name)
+        else:
+            self.surname_list.insert(i, name)
 
     def remove_from_surname_list(self, person):
         """
@@ -1466,13 +1475,16 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         If not then we need to remove the name from the list.
         The function must be overridden in the derived class.
         """
-        name = self._order_by_person_key(person)
-        #if isinstance(name, str):
-        #    uname = name
-        #    name = name.encode('utf-8')
-        #else:
-        #    uname = str(name)
-        # FIXME: check database
+        name = None
+        primary_name = person.get_primary_name()
+        if primary_name:
+            surname_list = primary_name.get_surname_list()
+            if len(surname_list) > 0:
+                name = surname_list[0].surname
+        if name is None:
+            return
+        if name in self.surname_list:
+            self.surname_list.remove(name)
 
     def commit_family(self, family, trans, change_time=None):
         emit = None
@@ -1486,13 +1498,13 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 family.handle])
         else:
             emit = "family-add"
-            self.dbapi.execute("""insert into family (handle, gramps_id, blob)
-                    values(?, ?, ?);""", 
+            self.dbapi.execute("""INSERT INTO family (handle, gramps_id, blob)
+                    VALUES(?, ?, ?);""", 
                                [family.handle, family.gramps_id, 
                                 pickle.dumps(family.serialize())])
         if not trans.batch:
-            self.dbapi.commit()
             self.update_backlinks(family)
+            self.dbapi.commit()
         # Misc updates:
         self.family_attributes.update(
             [str(attr.type) for attr in family.attribute_list
@@ -1537,15 +1549,15 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 citation.handle])
         else:
             emit = "citation-add"
-            self.dbapi.execute("""insert into citation (handle, order_by, gramps_id, blob)
-                     values(?, ?, ?, ?);""", 
+            self.dbapi.execute("""INSERT INTO citation (handle, order_by, gramps_id, blob)
+                     VALUES(?, ?, ?, ?);""", 
                        [citation.handle, 
                         self._order_by_citation_key(citation),
                         citation.gramps_id, 
                         pickle.dumps(citation.serialize())])
         if not trans.batch:
-            self.dbapi.commit()
             self.update_backlinks(citation)
+            self.dbapi.commit()
         # Misc updates:
         attr_list = []
         for mref in citation.media_list:
@@ -1576,15 +1588,15 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 source.handle])
         else:
             emit = "source-add"
-            self.dbapi.execute("""insert into source (handle, order_by, gramps_id, blob)
-                    values(?, ?, ?, ?);""", 
+            self.dbapi.execute("""INSERT INTO source (handle, order_by, gramps_id, blob)
+                    VALUES(?, ?, ?, ?);""", 
                        [source.handle, 
                         self._order_by_source_key(source),
                         source.gramps_id, 
                         pickle.dumps(source.serialize())])
         if not trans.batch:
-            self.dbapi.commit()
             self.update_backlinks(source)
+            self.dbapi.commit()
         # Misc updates:
         self.source_media_types.update(
             [str(ref.media_type) for ref in source.reporef_list
@@ -1615,12 +1627,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 repository.handle])
         else:
             emit = "repository-add"
-            self.dbapi.execute("""insert into repository (handle, gramps_id, blob)
-                     values(?, ?, ?);""", 
+            self.dbapi.execute("""INSERT INTO repository (handle, gramps_id, blob)
+                     VALUES(?, ?, ?);""", 
                        [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())])
         if not trans.batch:
-            self.dbapi.commit()
             self.update_backlinks(repository)
+            self.dbapi.commit()
         # Misc updates:
         if repository.type.is_custom():
             self.repository_types.add(str(repository.type))
@@ -1644,12 +1656,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 note.handle])
         else:
             emit = "note-add"
-            self.dbapi.execute("""insert into note (handle, gramps_id, blob)
-                     values(?, ?, ?);""", 
+            self.dbapi.execute("""INSERT INTO note (handle, gramps_id, blob)
+                     VALUES(?, ?, ?);""", 
                        [note.handle, note.gramps_id, pickle.dumps(note.serialize())])
         if not trans.batch:
-            self.dbapi.commit()
             self.update_backlinks(note)
+            self.dbapi.commit()
         # Misc updates:
         if note.type.is_custom():
             self.note_types.add(str(note.type))        
@@ -1672,15 +1684,15 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 place.handle])
         else:
             emit = "place-add"
-            self.dbapi.execute("""insert into place (handle, order_by, gramps_id, blob)
-                    values(?, ?, ?, ?);""", 
+            self.dbapi.execute("""INSERT INTO place (handle, order_by, gramps_id, blob)
+                    VALUES(?, ?, ?, ?);""", 
                        [place.handle, 
                         self._order_by_place_key(place),
                         place.gramps_id, 
                         pickle.dumps(place.serialize())])
         if not trans.batch:
-            self.dbapi.commit()
             self.update_backlinks(place)
+            self.dbapi.commit()
         # Misc updates:
         if place.get_type().is_custom():
             self.place_types.add(str(place.get_type()))
@@ -1710,14 +1722,14 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 event.handle])
         else:
             emit = "event-add"
-            self.dbapi.execute("""insert into event (handle, gramps_id, blob)
-                  values(?, ?, ?);""", 
+            self.dbapi.execute("""INSERT INTO event (handle, gramps_id, blob)
+                  VALUES(?, ?, ?);""", 
                        [event.handle, 
                         event.gramps_id, 
                         pickle.dumps(event.serialize())])
         if not trans.batch:
-            self.dbapi.commit()
             self.update_backlinks(event)
+            self.dbapi.commit()
         # Misc updates:
         self.event_attributes.update(
             [str(attr.type) for attr in event.attribute_list
@@ -1736,27 +1748,19 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def update_backlinks(self, obj):
         # First, delete the current references:
-        self.dbapi.execute("DELETE FROM reference where obj_handle = ?;",
+        self.dbapi.execute("DELETE FROM reference WHERE obj_handle = ?;",
                            [obj.handle])
         # Now, add the current ones:
         references = set(obj.get_referenced_handles_recursively())
         for (ref_class_name, ref_handle) in references:
-            self.dbapi.execute("""INSERT into reference 
+            self.dbapi.execute("""INSERT INTO reference 
                        (obj_handle, obj_class, ref_handle, ref_class)
                        VALUES(?, ?, ?, ?);""",
                                [obj.handle, 
                                 obj.__class__.__name__,
                                 ref_handle, 
                                 ref_class_name])
-        # Will commit later
-
-    def update_event_attributes(self, attr_list):
-        # FIXME
-        pass
-
-    def update_event_names(self, event_type):
-        # FIXME
-        pass
+        # This function is followed by a commit.
 
     def commit_tag(self, tag, trans, change_time=None):
         emit = None
@@ -1770,14 +1774,14 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 tag.handle])
         else:
             emit = "tag-add"
-            self.dbapi.execute("""insert into tag (handle, order_by, blob)
-                  values(?, ?, ?);""", 
+            self.dbapi.execute("""INSERT INTO tag (handle, order_by, blob)
+                  VALUES(?, ?, ?);""", 
                        [tag.handle, 
                         self._order_by_tag_key(tag),
                         pickle.dumps(tag.serialize())])
         if not trans.batch:
-            self.dbapi.commit()
             self.update_backlinks(tag)
+            self.dbapi.commit()
         # Emit after added:
         if emit:
             self.emit(emit, ([tag.handle],))
@@ -1796,15 +1800,15 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 media.handle])
         else:
             emit = "media-add"
-            self.dbapi.execute("""insert into media (handle, order_by, gramps_id, blob)
-                  values(?, ?, ?, ?);""", 
+            self.dbapi.execute("""INSERT INTO media (handle, order_by, gramps_id, blob)
+                  VALUES(?, ?, ?, ?);""", 
                        [media.handle, 
                         self._order_by_media_key(media),
                         media.gramps_id, 
                         pickle.dumps(media.serialize())])
         if not trans.batch:
-            self.dbapi.commit()
             self.update_backlinks(media)
+            self.dbapi.commit()
         # Misc updates:
         self.media_attributes.update(
             [str(attr.type) for attr in media.attribute_list
@@ -1910,7 +1914,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             person = Person.create(self.person_map[handle])
             #del self.person_map[handle]
             #del self.person_id_map[person.gramps_id]
-            self.dbapi.execute("DELETE from person WHERE handle = ?;", [handle])
+            self.dbapi.execute("DELETE FROM person WHERE handle = ?;", [handle])
             self.emit("person-delete", ([handle],))
             if not transaction.batch:
                 self.dbapi.commit()
@@ -2011,7 +2015,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if self.readonly or not handle:
             return
         if handle in data_map:
-            self.dbapi.execute("DELETE from %s WHERE handle = ?;" % key2table[key], 
+            self.dbapi.execute("DELETE FROM %s WHERE handle = ?;" % key2table[key], 
                                [handle])
             self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],))
             if not transaction.batch:
@@ -2078,7 +2082,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
             result_list = list(find_backlink_handles(handle))
         """
-        cur = self.dbapi.execute("SELECT * from reference WHERE ref_handle = ?;",
+        cur = self.dbapi.execute("SELECT * FROM reference WHERE ref_handle = ?;",
                                  [handle])
         rows = cur.fetchall()
         for row in rows:
@@ -2553,7 +2557,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                     references = set(obj.get_referenced_handles_recursively())
                     # handle addition of new references
                     for (ref_class_name, ref_handle) in references:
-                        self.dbapi.execute("""INSERT into reference (obj_handle, obj_class, ref_handle, ref_class)
+                        self.dbapi.execute("""INSERT INTO reference (obj_handle, obj_class, ref_handle, ref_class)
                                                  VALUES(?, ?, ?, ?);""",
                                            [obj.handle, 
                                             obj.__class__.__name__,
@@ -2564,199 +2568,217 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         callback(5)
 
     def rebuild_secondary(self, update):
-        self.reindex_reference_map(update)
+        pass
+        # FIXME: rebuild the secondary databases/maps:
+        ## gender stats
+        ## event_names
+        ## fattr_names
+        ## pattr_names
+        ## sattr_names
+        ## marker_names
+        ## child_refs
+        ## family_rels
+        ## event_roles
+        ## name_types
+        ## origin_types
+        ## repo_types
+        ## note_types
+        ## sm_types
+        ## url_types
+        ## mattr_names
+        ## eattr_names
+        ## place_types
+        # surname list
 
     def prepare_import(self):
         """
-        DBAPI does not commit data on gedcom import, but saves them
-        for later commit.
+        Do anything needed before an import.
         """
         pass
 
     def commit_import(self):
         """
-        Commits the items that were queued up during the last gedcom
-        import for two step adding.
+        Do anything needed after an import.
         """
         self.reindex_reference_map(lambda n: n)
 
     def has_handle_for_person(self, key):
-        cur = self.dbapi.execute("select * from person where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM person WHERE handle = ?", [key])
         return cur.fetchone() != None
 
     def has_handle_for_family(self, key):
-        cur = self.dbapi.execute("select * from family where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM family WHERE handle = ?", [key])
         return cur.fetchone() != None
 
     def has_handle_for_source(self, key):
-        cur = self.dbapi.execute("select * from source where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM source WHERE handle = ?", [key])
         return cur.fetchone() != None
 
     def has_handle_for_citation(self, key):
-        cur = self.dbapi.execute("select * from citation where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM citation WHERE handle = ?", [key])
         return cur.fetchone() != None
 
     def has_handle_for_event(self, key):
-        cur = self.dbapi.execute("select * from event where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM event WHERE handle = ?", [key])
         return cur.fetchone() != None
 
     def has_handle_for_media(self, key):
-        cur = self.dbapi.execute("select * from media where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM media WHERE handle = ?", [key])
         return cur.fetchone() != None
 
     def has_handle_for_place(self, key):
-        cur = self.dbapi.execute("select * from place where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM place WHERE handle = ?", [key])
         return cur.fetchone() != None
 
     def has_handle_for_repository(self, key):
-        cur = self.dbapi.execute("select * from repository where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM repository WHERE handle = ?", [key])
         return cur.fetchone() != None
 
     def has_handle_for_note(self, key):
-        cur = self.dbapi.execute("select * from note where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM note WHERE handle = ?", [key])
         return cur.fetchone() != None
 
     def has_handle_for_tag(self, key):
-        cur = self.dbapi.execute("select * from tag where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM tag WHERE handle = ?", [key])
         return cur.fetchone() != None
 
     def has_gramps_id_for_person(self, key):
-        cur = self.dbapi.execute("select * from person where gramps_id = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM person WHERE gramps_id = ?", [key])
         return cur.fetchone() != None
 
     def has_gramps_id_for_family(self, key):
-        cur = self.dbapi.execute("select * from family where gramps_id = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM family WHERE gramps_id = ?", [key])
         return cur.fetchone() != None
 
     def has_gramps_id_for_source(self, key):
-        cur = self.dbapi.execute("select * from source where gramps_id = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM source WHERE gramps_id = ?", [key])
         return cur.fetchone() != None
 
     def has_gramps_id_for_citation(self, key):
-        cur = self.dbapi.execute("select * from citation where gramps_id = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM citation WHERE gramps_id = ?", [key])
         return cur.fetchone() != None
 
     def has_gramps_id_for_event(self, key):
-        cur = self.dbapi.execute("select * from event where gramps_id = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM event WHERE gramps_id = ?", [key])
         return cur.fetchone() != None
 
     def has_gramps_id_for_media(self, key):
-        cur = self.dbapi.execute("select * from media where gramps_id = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM media WHERE gramps_id = ?", [key])
         return cur.fetchone() != None
 
     def has_gramps_id_for_place(self, key):
-        cur = self.dbapi.execute("select * from place where gramps_id = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM place WHERE gramps_id = ?", [key])
         return cur.fetchone() != None
 
     def has_gramps_id_for_repository(self, key):
-        cur = self.dbapi.execute("select * from repository where gramps_id = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM repository WHERE gramps_id = ?", [key])
         return cur.fetchone() != None
 
     def has_gramps_id_for_note(self, key):
-        cur = self.dbapi.execute("select * from note where gramps_id = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM note WHERE gramps_id = ?", [key])
         return cur.fetchone() != None
 
     def get_person_gramps_ids(self):
-        cur = self.dbapi.execute("select gramps_id from person;")
+        cur = self.dbapi.execute("SELECT gramps_id FROM person;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_family_gramps_ids(self):
-        cur = self.dbapi.execute("select gramps_id from family;")
+        cur = self.dbapi.execute("SELECT gramps_id FROM family;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_source_gramps_ids(self):
-        cur = self.dbapi.execute("select gramps_id from source;")
+        cur = self.dbapi.execute("SELECT gramps_id FROM source;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_citation_gramps_ids(self):
-        cur = self.dbapi.execute("select gramps_id from citation;")
+        cur = self.dbapi.execute("SELECT gramps_id FROM citation;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_event_gramps_ids(self):
-        cur = self.dbapi.execute("select gramps_id from event;")
+        cur = self.dbapi.execute("SELECT gramps_id FROM event;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_media_gramps_ids(self):
-        cur = self.dbapi.execute("select gramps_id from media;")
+        cur = self.dbapi.execute("SELECT gramps_id FROM media;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_place_gramps_ids(self):
-        cur = self.dbapi.execute("select gramps from place;")
+        cur = self.dbapi.execute("SELECT gramps FROM place;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_repository_gramps_ids(self):
-        cur = self.dbapi.execute("select gramps_id from repository;")
+        cur = self.dbapi.execute("SELECT gramps_id FROM repository;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_note_gramps_ids(self):
-        cur = self.dbapi.execute("select gramps_id from note;")
+        cur = self.dbapi.execute("SELECT gramps_id FROM note;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def _get_raw_person_data(self, key):
-        cur = self.dbapi.execute("select blob from person where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT blob FROM person WHERE handle = ?", [key])
         row = cur.fetchone()
         if row:
             return pickle.loads(row[0])
 
     def _get_raw_family_data(self, key):
-        cur = self.dbapi.execute("select blob from family where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT blob FROM family WHERE handle = ?", [key])
         row = cur.fetchone()
         if row:
             return pickle.loads(row[0])
 
     def _get_raw_source_data(self, key):
-        cur = self.dbapi.execute("select blob from source where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT blob FROM source WHERE handle = ?", [key])
         row = cur.fetchone()
         if row:
             return pickle.loads(row[0])
 
     def _get_raw_citation_data(self, key):
-        cur = self.dbapi.execute("select blob from citation where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT blob FROM citation WHERE handle = ?", [key])
         row = cur.fetchone()
         if row:
             return pickle.loads(row[0])
 
     def _get_raw_event_data(self, key):
-        cur = self.dbapi.execute("select blob from event where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT blob FROM event WHERE handle = ?", [key])
         row = cur.fetchone()
         if row:
             return pickle.loads(row[0])
 
     def _get_raw_media_data(self, key):
-        cur = self.dbapi.execute("select blob from media where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT blob FROM media WHERE handle = ?", [key])
         row = cur.fetchone()
         if row:
             return pickle.loads(row[0])
 
     def _get_raw_place_data(self, key):
-        cur = self.dbapi.execute("select blob from place where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT blob FROM place WHERE handle = ?", [key])
         row = cur.fetchone()
         if row:
             return pickle.loads(row[0])
 
     def _get_raw_repository_data(self, key):
-        cur = self.dbapi.execute("select blob from repository where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT blob FROM repository WHERE handle = ?", [key])
         row = cur.fetchone()
         if row:
             return pickle.loads(row[0])
 
     def _get_raw_note_data(self, key):
-        cur = self.dbapi.execute("select blob from note where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT blob FROM note WHERE handle = ?", [key])
         row = cur.fetchone()
         if row:
             return pickle.loads(row[0])
 
     def _get_raw_tag_data(self, key):
-        cur = self.dbapi.execute("select blob from tag where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT blob FROM tag WHERE handle = ?", [key])
         row = cur.fetchone()
         if row:
             return pickle.loads(row[0])

From 90488ce14b755aaf91e1a7b84e7a174b017b3f8e Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Tue, 26 May 2015 11:35:56 -0400
Subject: [PATCH 050/105] DB-API: fixed error in find_initial_person

---
 gramps/plugins/database/dbapi.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index 51a409240..54ca72cea 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -2099,7 +2099,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         cur = self.dbapi.execute("SELECT handle FROM person;")
         row = cur.fetchone()
         if row:
-            return row[0]
+            return self.get_person_from_handle(row[0])
 
     def get_bookmarks(self):
         return self.bookmarks

From b059bdec66361d9e89f28b5dc215a31ce1bcf8fb Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Tue, 12 May 2015 16:30:46 -0400
Subject: [PATCH 051/105] Database backend as a plugin: this set of changes
 moves most or all of Bsddb from gramps.gen.db to gramps.plugins.database. The
 id of the plugin is 'bsddb' which can be loaded using the make_database(id,
 dbstate) API (for now).

Next step is to add an identifying text in the directory to
indicate which database backend to use.
---
 gramps/cli/arghandler.py                      |   5 +-
 gramps/cli/clidbman.py                        |   9 +-
 gramps/cli/grampscli.py                       |   6 +-
 gramps/gen/db/__init__.py                     |  27 ++-
 gramps/gen/db/backup.py                       | 209 +----------------
 gramps/gen/dbstate.py                         |   6 +-
 gramps/gen/plug/_gramplet.py                  |   1 -
 gramps/gen/plug/_manager.py                   |   5 +
 gramps/gen/plug/_pluginreg.py                 |  31 ++-
 gramps/gen/utils/callman.py                   |   3 +-
 gramps/gui/dbloader.py                        |   4 +-
 gramps/gui/dbman.py                           |  11 +-
 gramps/gui/pluginmanager.py                   |   7 +-
 gramps/plugins/database/bsddb.gpr.py          |  31 +++
 gramps/plugins/database/bsddb.py              |   3 +
 .../database/bsddb_support/__init__.py        |  96 ++++++++
 .../plugins/database/bsddb_support/backup.py  | 213 ++++++++++++++++++
 .../database/bsddb_support}/bsddbtxn.py       |   0
 .../database/bsddb_support}/cursor.py         |   0
 .../database/bsddb_support}/dictionary.py     |  27 +--
 .../database/bsddb_support}/read.py           |  45 ++--
 .../database/bsddb_support}/undoredo.py       |   8 +-
 .../database/bsddb_support}/upgrade.py        |  31 +--
 .../database/bsddb_support}/write.py          |  45 ++--
 24 files changed, 507 insertions(+), 316 deletions(-)
 create mode 100644 gramps/plugins/database/bsddb.gpr.py
 create mode 100644 gramps/plugins/database/bsddb.py
 create mode 100644 gramps/plugins/database/bsddb_support/__init__.py
 create mode 100644 gramps/plugins/database/bsddb_support/backup.py
 rename gramps/{gen/db => plugins/database/bsddb_support}/bsddbtxn.py (100%)
 rename gramps/{gen/db => plugins/database/bsddb_support}/cursor.py (100%)
 rename gramps/{gen/db => plugins/database/bsddb_support}/dictionary.py (98%)
 rename gramps/{gen/db => plugins/database/bsddb_support}/read.py (98%)
 rename gramps/{gen/db => plugins/database/bsddb_support}/undoredo.py (98%)
 rename gramps/{gen/db => plugins/database/bsddb_support}/upgrade.py (98%)
 rename gramps/{gen/db => plugins/database/bsddb_support}/write.py (99%)

diff --git a/gramps/cli/arghandler.py b/gramps/cli/arghandler.py
index 0db169d30..c7a5f5a4a 100644
--- a/gramps/cli/arghandler.py
+++ b/gramps/cli/arghandler.py
@@ -44,7 +44,7 @@ import sys
 #-------------------------------------------------------------------------
 from gramps.gen.recentfiles import recent_files
 from gramps.gen.utils.file import rm_tempdir, get_empty_tempdir
-from gramps.gen.db import DbBsddb
+from gramps.gen.db import make_database
 from .clidbman import CLIDbManager, NAME_FILE, find_locker_name
 
 from gramps.gen.plug import BasePluginManager
@@ -491,7 +491,8 @@ class ArgHandler(object):
                     self.imp_db_path, title = self.dbman.create_new_db_cli()
                 else:
                     self.imp_db_path = get_empty_tempdir("import_dbdir")
-                    newdb = DbBsddb()
+
+                    newdb = make_database("bsddb", self.dbstate)
                     newdb.write_version(self.imp_db_path)
                 
                 try:
diff --git a/gramps/cli/clidbman.py b/gramps/cli/clidbman.py
index 404104afe..52b183013 100644
--- a/gramps/cli/clidbman.py
+++ b/gramps/cli/clidbman.py
@@ -54,7 +54,7 @@ _LOG = logging.getLogger(DBLOGNAME)
 #-------------------------------------------------------------------------
 from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
-from gramps.gen.db import DbBsddb
+from gramps.gen.db import make_database
 from gramps.gen.plug import BasePluginManager
 from gramps.gen.config import config
 from gramps.gen.constfunc import win, conv_to_unicode
@@ -294,7 +294,8 @@ class CLIDbManager(object):
 
         if create_db:
             # write the version number into metadata
-            newdb = DbBsddb()
+            
+            newdb = make_database("bsddb", self.dbstate)
             newdb.write_version(new_path)
 
         (tval, last) = time_val(new_path)
@@ -360,8 +361,8 @@ class CLIDbManager(object):
     
                 # Create a new database
                 self.__start_cursor(_("Importing data..."))
-                dbclass = DbBsddb
-                dbase = dbclass()
+
+                dbase = make_database("bsddb", self.dbstate)
                 dbase.load(new_path, user.callback)
     
                 import_function = plugin.get_import_function()
diff --git a/gramps/cli/grampscli.py b/gramps/cli/grampscli.py
index a45564a19..00b66d352 100644
--- a/gramps/cli/grampscli.py
+++ b/gramps/cli/grampscli.py
@@ -49,7 +49,7 @@ from gramps.gen.config import config
 from gramps.gen.const import PLUGINS_DIR, USER_PLUGINS
 from gramps.gen.errors import DbError
 from gramps.gen.dbstate import DbState
-from gramps.gen.db import DbBsddb
+from gramps.gen.db import make_database
 from gramps.gen.db.exceptions import (DbUpgradeRequiredError, 
                                       BsddbDowngradeError, 
                                       DbVersionError, 
@@ -152,9 +152,9 @@ class CLIDbLoader(object):
         else:
             mode = 'w'
 
-        dbclass = DbBsddb
+        db = make_database("bsddb", self.dbstate)
         
-        self.dbstate.change_database(dbclass())
+        self.dbstate.change_database(db)
         self.dbstate.db.disable_signals()
 
         self._begin_progress()
diff --git a/gramps/gen/db/__init__.py b/gramps/gen/db/__init__.py
index 5b79ce334..b977887b7 100644
--- a/gramps/gen/db/__init__.py
+++ b/gramps/gen/db/__init__.py
@@ -86,11 +86,26 @@ More details can be found in the manual's
 
 from .base import *
 from .dbconst import *
-from .cursor import *
-from .read import *
-from .bsddbtxn import *
+#from .cursor import *
+#from .read import *
+#from .bsddbtxn import *
 from .txn import *
-from .undoredo import *
+#from .undoredo import *
 from .exceptions import *
-from .write import *
-from .backup import backup, restore
+#from .write import *
+#from .backup import backup, restore
+
+def make_database(id, dbstate):
+    from gramps.cli.grampscli import CLIManager
+    from gramps.gen.plug import BasePluginManager
+    from gramps.cli.user import User
+
+    climanager = CLIManager(dbstate, setloader=False, user=User()) # do not load db_loader
+    climanager.do_reg_plugins(dbstate, None)
+    pmgr = BasePluginManager.get_instance()
+    pdata = pmgr.get_plugin(id)
+
+    mod = pmgr.load_plugin(pdata)
+    database = getattr(mod, pdata.databaseclass)
+    return database()
+
diff --git a/gramps/gen/db/backup.py b/gramps/gen/db/backup.py
index 9329caaff..52d4d5f05 100644
--- a/gramps/gen/db/backup.py
+++ b/gramps/gen/db/backup.py
@@ -1,213 +1,8 @@
-#
-# Gramps - a GTK+/GNOME based genealogy program
-#
-# Copyright (C) 2007  Donald N. Allingham
-# Copyright (C) 2011       Tim G L Lyons
-#
-# 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; either version 2 of the License, or
-# (at your option) any later version.
-#
-# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-# gen/db/backup.py
-
-"""
-Description
-===========
-
-This module Provides backup and restore functions for a database. The
-backup function saves the data into backup files, while the restore
-function loads the data back into a database.
-
-You should only restore the data into an empty database.
-
-Implementation
-==============
-
-Not all of the database tables need to be backed up, since many are
-automatically generated from the others. The tables that are backed up
-are the primary tables and the metadata table.
-
-The database consists of a table of "pickled" tuples. Each of the
-primary tables is "walked", and the pickled tuple is extracted, and
-written to the backup file.
-
-Restoring the data is just as simple. The backup file is parsed an
-entry at a time, and inserted into the associated database table. The
-derived tables are built automatically as the items are entered into
-db.
-"""
-
-#-------------------------------------------------------------------------
-#
-# load standard python libraries
-#
-#-------------------------------------------------------------------------
-import os
-import pickle
-
-#------------------------------------------------------------------------
-#
-# Gramps libs
-#
-#------------------------------------------------------------------------
-from .exceptions import DbException
-from .write import FAMILY_TBL, PLACES_TBL, SOURCES_TBL, MEDIA_TBL, \
-    EVENTS_TBL, PERSON_TBL, REPO_TBL, NOTE_TBL, TAG_TBL, META, CITATIONS_TBL
-
-#------------------------------------------------------------------------
-#
-# Set up logging
-#
-#------------------------------------------------------------------------
 import logging
 LOG = logging.getLogger(".Backup")
 
 def backup(database):
-    """
-    Exports the database to a set of backup files. These files consist
-    of the pickled database tables, one file for each table.
-
-    The heavy lifting is done by the private :py:func:`__do__export` function.
-    The purpose of this function is to catch any exceptions that occur.
-
-    :param database: database instance to backup
-    :type database: DbDir
-    """
-    try:
-        __do_export(database)
-    except (OSError, IOError) as msg:
-        raise DbException(str(msg))
-
-def __mk_backup_name(database, base):
-    """
-    Return the backup name of the database table
-
-    :param database: database instance 
-    :type database: DbDir
-    :param base: base name of the table
-    :type base: str
-    """
-    return os.path.join(database.get_save_path(), base + ".gbkp")
-
-def __mk_tmp_name(database, base):
-    """
-    Return the temporary backup name of the database table
-
-    :param database: database instance 
-    :type database: DbDir
-    :param base: base name of the table
-    :type base: str
-    """
-    return os.path.join(database.get_save_path(), base + ".gbkp.new")
-
-def __do_export(database):
-    """
-    Loop through each table of the database, saving the pickled data
-    a file.
-
-    :param database: database instance to backup
-    :type database: DbDir
-    """
-    try:
-        for (base, tbl) in __build_tbl_map(database):
-            backup_name = __mk_tmp_name(database, base)
-            backup_table = open(backup_name, 'wb')
-    
-            cursor = tbl.cursor()
-            data = cursor.first()
-            while data:
-                pickle.dump(data, backup_table, 2)
-                data = cursor.next()
-            cursor.close()
-            backup_table.close()
-    except (IOError,OSError):
-        return
-
-    for (base, tbl) in __build_tbl_map(database):
-        new_name = __mk_backup_name(database, base)
-        old_name = __mk_tmp_name(database, base)
-        if os.path.isfile(new_name):
-            os.unlink(new_name)
-        os.rename(old_name, new_name)
+    print("FIXME")
 
 def restore(database):
-    """
-    Restores the database to a set of backup files. These files consist
-    of the pickled database tables, one file for each table.
-
-    The heavy lifting is done by the private :py:func:`__do__restore` function.
-    The purpose of this function is to catch any exceptions that occur.
-
-    :param database: database instance to restore
-    :type database: DbDir
-    """
-    try:
-        __do_restore(database)
-    except (OSError, IOError) as msg:
-        raise DbException(str(msg))
-
-def __do_restore(database):
-    """
-    Loop through each table of the database, restoring the pickled data
-    to the appropriate database file.
-
-    :param database: database instance to backup
-    :type database: DbDir
-    """
-    for (base, tbl) in __build_tbl_map(database):
-        backup_name = __mk_backup_name(database, base)
-        backup_table = open(backup_name, 'rb')
-        __load_tbl_txn(database, backup_table, tbl)
-
-    database.rebuild_secondary()
-
-def __load_tbl_txn(database, backup_table, tbl):
-    """
-    Return the temporary backup name of the database table
-
-    :param database: database instance 
-    :type database: DbDir
-    :param backup_table: file containing the backup data
-    :type backup_table: file
-    :param tbl: Berkeley db database table
-    :type tbl: Berkeley db database table
-    """
-    try:
-        while True:
-            data = pickle.load(backup_table)
-            txn = database.env.txn_begin()
-            tbl.put(data[0], data[1], txn=txn)
-            txn.commit()
-    except EOFError:
-        backup_table.close()
-
-def __build_tbl_map(database):
-    """
-    Builds a table map of names to database tables.
-
-    :param database: database instance to backup
-    :type database: DbDir
-    """
-    return [
-        ( PERSON_TBL,  database.person_map.db),
-        ( FAMILY_TBL,  database.family_map.db),
-        ( PLACES_TBL,  database.place_map.db),
-        ( SOURCES_TBL, database.source_map.db),
-        ( CITATIONS_TBL, database.citation_map.db),
-        ( REPO_TBL,    database.repository_map.db),
-        ( NOTE_TBL,    database.note_map.db),
-        ( MEDIA_TBL,   database.media_map.db),
-        ( EVENTS_TBL,  database.event_map.db),
-        ( TAG_TBL,     database.tag_map.db),
-        ( META,        database.metadata.db),
-        ]
+    print("FIXME")
diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py
index 839d05cd2..12ee68ea9 100644
--- a/gramps/gen/dbstate.py
+++ b/gramps/gen/dbstate.py
@@ -23,8 +23,8 @@
 Provide the database state class
 """
 
-from .db import DbBsddbRead
 from .db import DbReadBase
+from .db import make_database
 from .proxy.proxybase import ProxyDbBase
 from .utils.callback import Callback
 from .config import config
@@ -45,7 +45,7 @@ class DbState(Callback):
         just a place holder until a real DB is assigned.
         """
         Callback.__init__(self)
-        self.db      = DbBsddbRead()
+        self.db      = make_database("bsddb", self)
         self.open    = False
         self.stack = []
 
@@ -88,7 +88,7 @@ class DbState(Callback):
         """
         self.emit('no-database', ())
         self.db.close()
-        self.db = DbBsddbRead()
+        self.db = make_database("bsddb", self)
         self.db.db_is_open = False
         self.open = False
         self.emit('database-changed', (self.db, ))
diff --git a/gramps/gen/plug/_gramplet.py b/gramps/gen/plug/_gramplet.py
index c040bae7d..b812a7fb7 100644
--- a/gramps/gen/plug/_gramplet.py
+++ b/gramps/gen/plug/_gramplet.py
@@ -70,7 +70,6 @@ class Gramplet(object):
         self.connect(self.gui.textview, "motion-notify-event", 
                      self.gui.on_motion)
         self.connect_signal('Person', self._active_changed)
-        
         self._db_changed(self.dbstate.db)
         active_person = self.get_active('Person')
         if active_person: # already changed
diff --git a/gramps/gen/plug/_manager.py b/gramps/gen/plug/_manager.py
index e047f6166..e30a089ae 100644
--- a/gramps/gen/plug/_manager.py
+++ b/gramps/gen/plug/_manager.py
@@ -412,6 +412,11 @@ class BasePluginManager(object):
         """
         return self.__pgr.sidebar_plugins()
     
+    def get_reg_databases(self):
+        """ Return list of registered database backends
+        """
+        return self.__pgr.database_plugins()
+    
     def get_external_opt_dict(self):
         """ Return the dictionary of external options. """
         return self.__external_opt_dict
diff --git a/gramps/gen/plug/_pluginreg.py b/gramps/gen/plug/_pluginreg.py
index 04167cafc..5619f18ed 100644
--- a/gramps/gen/plug/_pluginreg.py
+++ b/gramps/gen/plug/_pluginreg.py
@@ -70,8 +70,9 @@ VIEW        = 8
 RELCALC     = 9
 GRAMPLET    = 10
 SIDEBAR     = 11
+DATABASE    = 12
 PTYPE       = [REPORT , QUICKREPORT, TOOL, IMPORT, EXPORT, DOCGEN, GENERAL,
-               MAPSERVICE, VIEW, RELCALC, GRAMPLET, SIDEBAR]
+               MAPSERVICE, VIEW, RELCALC, GRAMPLET, SIDEBAR, DATABASE]
 PTYPE_STR   = {
         REPORT: _('Report') , 
         QUICKREPORT: _('Quickreport'), 
@@ -85,6 +86,7 @@ PTYPE_STR   = {
         RELCALC: _('Relationships'), 
         GRAMPLET: _('Gramplet'),
         SIDEBAR: _('Sidebar'),
+        DATABASE: _('Database'),
         }
 
 #possible report categories
@@ -206,7 +208,7 @@ class PluginData(object):
        The python path where the plugin implementation can be found
     .. attribute:: ptype
        The plugin type. One of REPORT , QUICKREPORT, TOOL, IMPORT,
-        EXPORT, DOCGEN, GENERAL, MAPSERVICE, VIEW, GRAMPLET
+        EXPORT, DOCGEN, GENERAL, MAPSERVICE, VIEW, GRAMPLET, DATABASE
     .. attribute:: authors
        List of authors of the plugin, default=[]
     .. attribute:: authors_email
@@ -349,6 +351,11 @@ class PluginData(object):
        the plugin is appended to the list of plugins. If START, then the
        plugin is prepended. Only set START if you want a plugin to be the
        first in the order of plugins
+
+    Attributes for DATABASE plugins
+
+    .. attribute:: databaseclass
+       The class in the module that is the database class
     """
 
     def __init__(self):
@@ -421,6 +428,8 @@ class PluginData(object):
         self._menu_label = ''
         #VIEW and SIDEBAR attr
         self._order = END
+        #DATABASE attr
+        self._databaseclass = None
         #GENERAL attr
         self._data = []
         self._process = None
@@ -931,6 +940,17 @@ class PluginData(object):
 
     order = property(_get_order, _set_order)
   
+    #DATABASE attributes
+    def _set_databaseclass(self, databaseclass):
+        if not self._ptype == DATABASE:
+            raise ValueError('databaseclass may only be set for DATABASE plugins')
+        self._databaseclass = databaseclass
+
+    def _get_databaseclass(self):
+        return self._databaseclass
+
+    databaseclass = property(_get_databaseclass, _set_databaseclass)
+
     #GENERAL attr
     def _set_data(self, data):
         if not self._ptype in (GENERAL,):
@@ -1032,6 +1052,7 @@ def make_environment(**kwargs):
         'REPORT_MODE_CLI': REPORT_MODE_CLI,
         'TOOL_MODE_GUI': TOOL_MODE_GUI, 
         'TOOL_MODE_CLI': TOOL_MODE_CLI,
+        'DATABASE': DATABASE,
         'GRAMPSVERSION': GRAMPSVERSION,
         'START': START,
         'END': END,
@@ -1297,6 +1318,12 @@ class PluginRegister(object):
         """
         return self.type_plugins(SIDEBAR)
 
+    def database_plugins(self):
+        """
+        Return a list of :class:`PluginData` that are of type DATABASE
+        """
+        return self.type_plugins(DATABASE)
+
     def filter_load_on_reg(self):
         """
         Return a list of :class:`PluginData` that have load_on_reg == True
diff --git a/gramps/gen/utils/callman.py b/gramps/gen/utils/callman.py
index ca6a94d86..939a0f6fa 100644
--- a/gramps/gen/utils/callman.py
+++ b/gramps/gen/utils/callman.py
@@ -303,7 +303,8 @@ class CallbackManager(object):
         Do a custom db connect signal outside of the primary object ones
         managed automatically.
         """
-        self.custom_signal_keys.append(self.database.connect(name, callback))
+        if self.database:
+            self.custom_signal_keys.append(self.database.connect(name, callback))
     
     def __callbackcreator(self, signal, noarg=False):
         """
diff --git a/gramps/gui/dbloader.py b/gramps/gui/dbloader.py
index 6e7f85d5e..18a1c3281 100644
--- a/gramps/gui/dbloader.py
+++ b/gramps/gui/dbloader.py
@@ -55,7 +55,7 @@ from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
 from gramps.cli.grampscli import CLIDbLoader
 from gramps.gen.config import config
-from gramps.gen.db import DbBsddb
+from gramps.gen.db import make_database
 from gramps.gen.db.exceptions import (DbUpgradeRequiredError, 
                                       BsddbDowngradeError, 
                                       DbVersionError, 
@@ -305,7 +305,7 @@ class DbLoader(CLIDbLoader):
         else:
             mode = 'w'
 
-        db = DbBsddb()
+        db = make_database("bsddb", self.dbstate)
         db.disable_signals()
         self.dbstate.no_database()
 
diff --git a/gramps/gui/dbman.py b/gramps/gui/dbman.py
index f3e6214e3..c5ccbe657 100644
--- a/gramps/gui/dbman.py
+++ b/gramps/gui/dbman.py
@@ -73,7 +73,7 @@ _ = glocale.translation.gettext
 from gramps.gen.const import URL_WIKISTRING
 from .user import User
 from .dialog import ErrorDialog, QuestionDialog, QuestionDialog2
-from gramps.gen.db import DbBsddb
+from gramps.gen.db import make_database
 from .pluginmanager import GuiPluginManager
 from gramps.cli.clidbman import CLIDbManager, NAME_FILE, time_val
 from .ddtargets import DdTargets
@@ -531,8 +531,8 @@ class DbManager(CLIDbManager):
         new_path, newname = self._create_new_db("%s : %s" % (parent_name, name))
         
         self.__start_cursor(_("Extracting archive..."))
-        dbclass = DbBsddb
-        dbase = dbclass()
+
+        dbase = make_database("bsddb", self.dbstate)
         dbase.load(new_path, None)
         
         self.__start_cursor(_("Importing archive..."))
@@ -719,11 +719,10 @@ class DbManager(CLIDbManager):
                 fname = os.path.join(dirname, filename)
                 os.unlink(fname)
 
-        newdb = DbBsddb()
+        newdb = make_database("bsddb", self.dbstate)
         newdb.write_version(dirname)
 
-        dbclass = DbBsddb
-        dbase = dbclass()
+        dbase = make_database("bsddb", self.dbstate)
         dbase.set_save_path(dirname)
         dbase.load(dirname, None)
 
diff --git a/gramps/gui/pluginmanager.py b/gramps/gui/pluginmanager.py
index edde82dac..bb609e984 100644
--- a/gramps/gui/pluginmanager.py
+++ b/gramps/gui/pluginmanager.py
@@ -205,7 +205,12 @@ class GuiPluginManager(Callback):
         return [plg for plg in self.basemgr.get_reg_docgens()
                                 if plg.id not in self.__hidden_plugins]
 
+    def get_reg_databases(self):
+        """ Return list of non hidden registered database backends
+        """
+        return [plg for plg in self.basemgr.get_reg_databases()
+                                if plg.id not in self.__hidden_plugins]
+
     def get_reg_general(self, category=None):
         return [plg for plg in self.basemgr.get_reg_general(category)
                                 if plg.id not in self.__hidden_plugins]
-
diff --git a/gramps/plugins/database/bsddb.gpr.py b/gramps/plugins/database/bsddb.gpr.py
new file mode 100644
index 000000000..20f2e9fda
--- /dev/null
+++ b/gramps/plugins/database/bsddb.gpr.py
@@ -0,0 +1,31 @@
+#
+# Gramps - a GTK+/GNOME based genealogy program
+#
+# Copyright (C) 2015 Douglas Blank <doug.blank@gmail.com>
+#
+# 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+plg = newplugin()
+plg.id    = 'bsddb'
+plg.name  = _("BSDDB Database Backend")
+plg.name_accell  = _("_BSDDB Database Backend")
+plg.description =  _("Berkeley Software Distribution Database Backend")
+plg.version = '1.0'
+plg.gramps_target_version = "4.2"
+plg.status = STABLE
+plg.fname = 'bsddb.py'
+plg.ptype = DATABASE
+plg.databaseclass = 'DbBsddb'
diff --git a/gramps/plugins/database/bsddb.py b/gramps/plugins/database/bsddb.py
new file mode 100644
index 000000000..9cbca25ce
--- /dev/null
+++ b/gramps/plugins/database/bsddb.py
@@ -0,0 +1,3 @@
+
+from bsddb_support import DbBsddb
+
diff --git a/gramps/plugins/database/bsddb_support/__init__.py b/gramps/plugins/database/bsddb_support/__init__.py
new file mode 100644
index 000000000..524ecb660
--- /dev/null
+++ b/gramps/plugins/database/bsddb_support/__init__.py
@@ -0,0 +1,96 @@
+#
+# Gramps - a GTK+/GNOME based genealogy program
+#
+# Copyright (C) 2000-2007  Donald N. Allingham
+#
+# 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+"""
+Gramps Database API.
+
+Database Architecture
+=====================
+
+Access to the database is made through Python classes. Exactly
+what functionality you have is dependent on the properties of the
+database. For example, if you are accessing a read-only view, then
+you will only have access to a subset of the methods available.
+
+At the root of any database interface is either :py:class:`.DbReadBase` and/or
+:py:class:`.DbWriteBase`. These define the methods to read and write to a
+database, respectively.
+
+The full database hierarchy is:
+
+- :py:class:`.DbBsddb` - read and write implementation to BSDDB databases
+
+  * :py:class:`.DbWriteBase` - virtual and implementation-independent methods
+    for reading data
+
+  * :py:class:`.DbBsddbRead` - read-only (accessors, getters) implementation
+    to BSDDB databases
+
+    + :py:class:`.DbReadBase` - virtual and implementation-independent
+      methods for reading data
+
+    + :py:class:`.Callback` - callback and signal functions
+
+  * :py:class:`.UpdateCallback` - callback functionality
+
+- :py:class:`.DbDjango` - read and write implementation to Django-based
+  databases
+
+  * :py:class:`.DbWriteBase` - virtual and implementation-independent methods
+    for reading data
+
+  * :py:class:`.DbReadBase` - virtual and implementation-independent methods
+    for reading data
+
+DbBsddb
+=======
+
+The :py:class:`.DbBsddb` interface defines a hierarchical database
+(non-relational) written in
+`PyBSDDB <http://www.jcea.es/programacion/pybsddb.htm>`_. There is no
+such thing as a database schema, and the meaning of the data is
+defined in the Python classes above. The data is stored as pickled
+tuples and unserialized into the primary data types (below).
+
+DbDjango
+========
+
+The DbDjango interface defines the Gramps data in terms of
+*models* and *relations* from the
+`Django project <http://www.djangoproject.com/>`_. The database
+backend can be any implementation that supports Django, including
+such popular SQL implementations as sqlite, MySQL, Postgresql, and
+Oracle. The data is retrieved from the SQL fields, serialized and
+then unserialized into the primary data types (below).
+
+More details can be found in the manual's
+`Using database API <http://www.gramps-project.org/wiki/index.php?title=Using_database_API>`_.
+"""
+
+from gramps.gen.db.base import *
+from gramps.gen.db.dbconst import *
+from .cursor import *
+from .read import *
+from .bsddbtxn import *
+from gramps.gen.db.txn import *
+from .undoredo import *
+from gramps.gen.db.exceptions import *
+from .write import *
+from .backup import backup, restore
diff --git a/gramps/plugins/database/bsddb_support/backup.py b/gramps/plugins/database/bsddb_support/backup.py
new file mode 100644
index 000000000..29dd46c54
--- /dev/null
+++ b/gramps/plugins/database/bsddb_support/backup.py
@@ -0,0 +1,213 @@
+#
+# Gramps - a GTK+/GNOME based genealogy program
+#
+# Copyright (C) 2007  Donald N. Allingham
+# Copyright (C) 2011       Tim G L Lyons
+#
+# 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# gen/db/backup.py
+
+"""
+Description
+===========
+
+This module Provides backup and restore functions for a database. The
+backup function saves the data into backup files, while the restore
+function loads the data back into a database.
+
+You should only restore the data into an empty database.
+
+Implementation
+==============
+
+Not all of the database tables need to be backed up, since many are
+automatically generated from the others. The tables that are backed up
+are the primary tables and the metadata table.
+
+The database consists of a table of "pickled" tuples. Each of the
+primary tables is "walked", and the pickled tuple is extracted, and
+written to the backup file.
+
+Restoring the data is just as simple. The backup file is parsed an
+entry at a time, and inserted into the associated database table. The
+derived tables are built automatically as the items are entered into
+db.
+"""
+
+#-------------------------------------------------------------------------
+#
+# load standard python libraries
+#
+#-------------------------------------------------------------------------
+import os
+import pickle
+
+#------------------------------------------------------------------------
+#
+# Gramps libs
+#
+#------------------------------------------------------------------------
+from gramps.gen.db.exceptions import DbException
+from .write import FAMILY_TBL, PLACES_TBL, SOURCES_TBL, MEDIA_TBL, \
+    EVENTS_TBL, PERSON_TBL, REPO_TBL, NOTE_TBL, TAG_TBL, META, CITATIONS_TBL
+
+#------------------------------------------------------------------------
+#
+# Set up logging
+#
+#------------------------------------------------------------------------
+import logging
+LOG = logging.getLogger(".Backup")
+
+def backup(database):
+    """
+    Exports the database to a set of backup files. These files consist
+    of the pickled database tables, one file for each table.
+
+    The heavy lifting is done by the private :py:func:`__do__export` function.
+    The purpose of this function is to catch any exceptions that occur.
+
+    :param database: database instance to backup
+    :type database: DbDir
+    """
+    try:
+        __do_export(database)
+    except (OSError, IOError) as msg:
+        raise DbException(str(msg))
+
+def __mk_backup_name(database, base):
+    """
+    Return the backup name of the database table
+
+    :param database: database instance 
+    :type database: DbDir
+    :param base: base name of the table
+    :type base: str
+    """
+    return os.path.join(database.get_save_path(), base + ".gbkp")
+
+def __mk_tmp_name(database, base):
+    """
+    Return the temporary backup name of the database table
+
+    :param database: database instance 
+    :type database: DbDir
+    :param base: base name of the table
+    :type base: str
+    """
+    return os.path.join(database.get_save_path(), base + ".gbkp.new")
+
+def __do_export(database):
+    """
+    Loop through each table of the database, saving the pickled data
+    a file.
+
+    :param database: database instance to backup
+    :type database: DbDir
+    """
+    try:
+        for (base, tbl) in __build_tbl_map(database):
+            backup_name = __mk_tmp_name(database, base)
+            backup_table = open(backup_name, 'wb')
+    
+            cursor = tbl.cursor()
+            data = cursor.first()
+            while data:
+                pickle.dump(data, backup_table, 2)
+                data = cursor.next()
+            cursor.close()
+            backup_table.close()
+    except (IOError,OSError):
+        return
+
+    for (base, tbl) in __build_tbl_map(database):
+        new_name = __mk_backup_name(database, base)
+        old_name = __mk_tmp_name(database, base)
+        if os.path.isfile(new_name):
+            os.unlink(new_name)
+        os.rename(old_name, new_name)
+
+def restore(database):
+    """
+    Restores the database to a set of backup files. These files consist
+    of the pickled database tables, one file for each table.
+
+    The heavy lifting is done by the private :py:func:`__do__restore` function.
+    The purpose of this function is to catch any exceptions that occur.
+
+    :param database: database instance to restore
+    :type database: DbDir
+    """
+    try:
+        __do_restore(database)
+    except (OSError, IOError) as msg:
+        raise DbException(str(msg))
+
+def __do_restore(database):
+    """
+    Loop through each table of the database, restoring the pickled data
+    to the appropriate database file.
+
+    :param database: database instance to backup
+    :type database: DbDir
+    """
+    for (base, tbl) in __build_tbl_map(database):
+        backup_name = __mk_backup_name(database, base)
+        backup_table = open(backup_name, 'rb')
+        __load_tbl_txn(database, backup_table, tbl)
+
+    database.rebuild_secondary()
+
+def __load_tbl_txn(database, backup_table, tbl):
+    """
+    Return the temporary backup name of the database table
+
+    :param database: database instance 
+    :type database: DbDir
+    :param backup_table: file containing the backup data
+    :type backup_table: file
+    :param tbl: Berkeley db database table
+    :type tbl: Berkeley db database table
+    """
+    try:
+        while True:
+            data = pickle.load(backup_table)
+            txn = database.env.txn_begin()
+            tbl.put(data[0], data[1], txn=txn)
+            txn.commit()
+    except EOFError:
+        backup_table.close()
+
+def __build_tbl_map(database):
+    """
+    Builds a table map of names to database tables.
+
+    :param database: database instance to backup
+    :type database: DbDir
+    """
+    return [
+        ( PERSON_TBL,  database.person_map.db),
+        ( FAMILY_TBL,  database.family_map.db),
+        ( PLACES_TBL,  database.place_map.db),
+        ( SOURCES_TBL, database.source_map.db),
+        ( CITATIONS_TBL, database.citation_map.db),
+        ( REPO_TBL,    database.repository_map.db),
+        ( NOTE_TBL,    database.note_map.db),
+        ( MEDIA_TBL,   database.media_map.db),
+        ( EVENTS_TBL,  database.event_map.db),
+        ( TAG_TBL,     database.tag_map.db),
+        ( META,        database.metadata.db),
+        ]
diff --git a/gramps/gen/db/bsddbtxn.py b/gramps/plugins/database/bsddb_support/bsddbtxn.py
similarity index 100%
rename from gramps/gen/db/bsddbtxn.py
rename to gramps/plugins/database/bsddb_support/bsddbtxn.py
diff --git a/gramps/gen/db/cursor.py b/gramps/plugins/database/bsddb_support/cursor.py
similarity index 100%
rename from gramps/gen/db/cursor.py
rename to gramps/plugins/database/bsddb_support/cursor.py
diff --git a/gramps/gen/db/dictionary.py b/gramps/plugins/database/bsddb_support/dictionary.py
similarity index 98%
rename from gramps/gen/db/dictionary.py
rename to gramps/plugins/database/bsddb_support/dictionary.py
index 36a8cd5fa..2d785ef6e 100644
--- a/gramps/gen/db/dictionary.py
+++ b/gramps/plugins/database/bsddb_support/dictionary.py
@@ -40,21 +40,18 @@ from . import (PERSON_KEY,
                NOTE_KEY,
                TAG_KEY)
 
-from ..const import GRAMPS_LOCALE as glocale
-_ = glocale.translation.gettext
-from ..errors import DbError
-from ..utils.id import create_id
-from ..lib.researcher import Researcher
-from ..lib.mediaobj import MediaObject
-from ..lib.person import Person
-from ..lib.family import Family
-from ..lib.src import Source
-from ..lib.citation import Citation
-from ..lib.event import Event
-from ..lib.place import Place
-from ..lib.repo import Repository
-from ..lib.note import Note
-from ..lib.tag import Tag
+from gramps.gen.utils.id import create_id
+from gramps.gen.lib.researcher import Researcher
+from gramps.gen.lib.mediaobj import MediaObject
+from gramps.gen.lib.person import Person
+from gramps.gen.lib.family import Family
+from gramps.gen.lib.src import Source
+from gramps.gen.lib.citation import Citation
+from gramps.gen.lib.event import Event
+from gramps.gen.lib.place import Place
+from gramps.gen.lib.repo import Repository
+from gramps.gen.lib.note import Note
+from gramps.gen.lib.tag import Tag
 
 class Cursor(object):
     """
diff --git a/gramps/gen/db/read.py b/gramps/plugins/database/bsddb_support/read.py
similarity index 98%
rename from gramps/gen/db/read.py
rename to gramps/plugins/database/bsddb_support/read.py
index 87dc112d6..d51810581 100644
--- a/gramps/gen/db/read.py
+++ b/gramps/plugins/database/bsddb_support/read.py
@@ -53,30 +53,32 @@ import logging
 # GRAMPS libraries
 #
 #-------------------------------------------------------------------------
-from ..lib.mediaobj import MediaObject
-from ..lib.person import Person
-from ..lib.family import Family
-from ..lib.src import Source
-from ..lib.citation import Citation
-from ..lib.event import Event
-from ..lib.place import Place
-from ..lib.repo import Repository
-from ..lib.note import Note
-from ..lib.tag import Tag
-from ..lib.genderstats import GenderStats
-from ..lib.researcher import Researcher 
-from ..lib.nameorigintype import NameOriginType
+from gramps.gen.lib.mediaobj import MediaObject
+from gramps.gen.lib.person import Person
+from gramps.gen.lib.family import Family
+from gramps.gen.lib.src import Source
+from gramps.gen.lib.citation import Citation
+from gramps.gen.lib.event import Event
+from gramps.gen.lib.place import Place
+from gramps.gen.lib.repo import Repository
+from gramps.gen.lib.note import Note
+from gramps.gen.lib.tag import Tag
+from gramps.gen.lib.genderstats import GenderStats
+from gramps.gen.lib.researcher import Researcher 
+from gramps.gen.lib.nameorigintype import NameOriginType
 
-from .dbconst import *
-from ..utils.callback import Callback
-from ..utils.cast import conv_dbstr_to_unicode
-from . import (BsddbBaseCursor, DbReadBase)
-from ..utils.id import create_id
-from ..errors import DbError
-from ..constfunc import handle2internal, get_env_var
-from ..const import GRAMPS_LOCALE as glocale
+from gramps.gen.utils.callback import Callback
+from gramps.gen.utils.cast import conv_dbstr_to_unicode
+from . import BsddbBaseCursor
+from gramps.gen.db.base import DbReadBase
+from gramps.gen.utils.id import create_id
+from gramps.gen.errors import DbError
+from gramps.gen.constfunc import handle2internal, get_env_var
+from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
 
+from gramps.gen.db.dbconst import *
+
 LOG = logging.getLogger(DBLOGNAME)
 LOG = logging.getLogger(".citation")
 #-------------------------------------------------------------------------
@@ -84,7 +86,6 @@ LOG = logging.getLogger(".citation")
 # constants
 #
 #-------------------------------------------------------------------------
-from .dbconst import *
 
 _SIGBASE = ('person', 'family', 'source', 'citation', 
             'event',  'media', 'place', 'repository',
diff --git a/gramps/gen/db/undoredo.py b/gramps/plugins/database/bsddb_support/undoredo.py
similarity index 98%
rename from gramps/gen/db/undoredo.py
rename to gramps/plugins/database/bsddb_support/undoredo.py
index 7fed4d876..da6b3368e 100644
--- a/gramps/gen/db/undoredo.py
+++ b/gramps/plugins/database/bsddb_support/undoredo.py
@@ -43,7 +43,7 @@ except:
         DBPageNotFoundError = 0
         DBInvalidArgError = 0
         
-from ..const import GRAMPS_LOCALE as glocale
+from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
 
 #-------------------------------------------------------------------------
@@ -51,10 +51,10 @@ _ = glocale.translation.gettext
 # Gramps modules
 #
 #-------------------------------------------------------------------------
-from ..constfunc import conv_to_unicode, handle2internal, win
-from .dbconst import *
+from gramps.gen.constfunc import conv_to_unicode, handle2internal, win
+from gramps.gen.db.dbconst import *
 from . import BSDDBTxn
-from ..errors import DbError
+from gramps.gen.errors import DbError
 
 #-------------------------------------------------------------------------
 #
diff --git a/gramps/gen/db/upgrade.py b/gramps/plugins/database/bsddb_support/upgrade.py
similarity index 98%
rename from gramps/gen/db/upgrade.py
rename to gramps/plugins/database/bsddb_support/upgrade.py
index c720a9547..1f1cd6719 100644
--- a/gramps/gen/db/upgrade.py
+++ b/gramps/plugins/database/bsddb_support/upgrade.py
@@ -39,23 +39,24 @@ from bsddb3 import db
 # Gramps modules
 #
 #-------------------------------------------------------------------------
-from ..const import GRAMPS_LOCALE as glocale
+from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
-from ..constfunc import handle2internal
-from ..lib.markertype import MarkerType
-from ..lib.nameorigintype import NameOriginType
-from ..lib.place import Place
-from ..lib.placeref import PlaceRef
-from ..lib.placetype import PlaceType
-from ..lib.placename import PlaceName
-from ..lib.eventtype import EventType
-from ..lib.tag import Tag
-from ..utils.file import create_checksum
-from ..utils.id import create_id
+from gramps.gen.constfunc import handle2internal
+from gramps.gen.lib.markertype import MarkerType
+from gramps.gen.lib.nameorigintype import NameOriginType
+from gramps.gen.lib.place import Place
+from gramps.gen.lib.placeref import PlaceRef
+from gramps.gen.lib.placetype import PlaceType
+from gramps.gen.lib.placename import PlaceName
+from gramps.gen.lib.eventtype import EventType
+from gramps.gen.lib.tag import Tag
+from gramps.gen.utils.file import create_checksum
+from gramps.gen.utils.id import create_id
 from . import BSDDBTxn
 from .write import _mkname, SURNAMES
-from .dbconst import (PERSON_KEY, FAMILY_KEY, EVENT_KEY, 
-                      MEDIA_KEY, PLACE_KEY, REPOSITORY_KEY, SOURCE_KEY)
+from gramps.gen.db.dbconst import (PERSON_KEY, FAMILY_KEY, EVENT_KEY, 
+                                   MEDIA_KEY, PLACE_KEY, REPOSITORY_KEY, 
+                                   SOURCE_KEY)
 from gramps.gui.dialog import (InfoDialog)
 
 LOG = logging.getLogger(".upgrade")
@@ -359,7 +360,7 @@ def upgrade_datamap_17(datamap):
     """
     new_srcattr_list = []
     private = False
-    from ..lib.srcattrtype import SrcAttributeType
+    from gramps.gen.lib.srcattrtype import SrcAttributeType
     for (key, value) in datamap.items():
         the_type = SrcAttributeType(key).serialize()
         new_srcattr_list.append((private, the_type, value))
diff --git a/gramps/gen/db/write.py b/gramps/plugins/database/bsddb_support/write.py
similarity index 99%
rename from gramps/gen/db/write.py
rename to gramps/plugins/database/bsddb_support/write.py
index 607735ec8..768db02d9 100644
--- a/gramps/gen/db/write.py
+++ b/gramps/plugins/database/bsddb_support/write.py
@@ -56,33 +56,34 @@ except:
 # Gramps modules
 #
 #-------------------------------------------------------------------------
-from ..lib.person import Person
-from ..lib.family import Family
-from ..lib.src import Source
-from ..lib.citation import Citation
-from ..lib.event import Event
-from ..lib.place import Place
-from ..lib.repo import Repository
-from ..lib.mediaobj import MediaObject
-from ..lib.note import Note
-from ..lib.tag import Tag
-from ..lib.genderstats import GenderStats
-from ..lib.researcher import Researcher 
+from gramps.gen.lib.person import Person
+from gramps.gen.lib.family import Family
+from gramps.gen.lib.src import Source
+from gramps.gen.lib.citation import Citation
+from gramps.gen.lib.event import Event
+from gramps.gen.lib.place import Place
+from gramps.gen.lib.repo import Repository
+from gramps.gen.lib.mediaobj import MediaObject
+from gramps.gen.lib.note import Note
+from gramps.gen.lib.tag import Tag
+from gramps.gen.lib.genderstats import GenderStats
+from gramps.gen.lib.researcher import Researcher 
 
 from . import (DbBsddbRead, DbWriteBase, BSDDBTxn, 
                     DbTxn, BsddbBaseCursor, BsddbDowngradeError, DbVersionError,
                     DbEnvironmentError, DbUpgradeRequiredError, find_surname,
-                    find_byte_surname, find_surname_name, DbUndoBSDDB as DbUndo,
-                    exceptions)
-from .dbconst import *
-from ..utils.callback import Callback
-from ..utils.cast import conv_dbstr_to_unicode
-from ..utils.id import create_id
-from ..updatecallback import UpdateCallback
-from ..errors import DbError
-from ..constfunc import (win, conv_to_unicode, handle2internal,
+                    find_byte_surname, find_surname_name, DbUndoBSDDB as DbUndo)
+
+from gramps.gen.db import exceptions
+from gramps.gen.db.dbconst import *
+from gramps.gen.utils.callback import Callback
+from gramps.gen.utils.cast import conv_dbstr_to_unicode
+from gramps.gen.utils.id import create_id
+from gramps.gen.updatecallback import UpdateCallback
+from gramps.gen.errors import DbError
+from gramps.gen.constfunc import (win, conv_to_unicode, handle2internal,
                          get_env_var)
-from ..const import HOME_DIR, GRAMPS_LOCALE as glocale
+from gramps.gen.const import HOME_DIR, GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
 
 _LOG = logging.getLogger(DBLOGNAME)

From 2d6a319c1399b833fe0c34596dc30d50df25b821 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Tue, 12 May 2015 19:09:17 -0400
Subject: [PATCH 052/105] Moved make_database to DbState

---
 gramps/cli/arghandler.py  |  3 +--
 gramps/cli/clidbman.py    |  5 ++---
 gramps/cli/grampscli.py   |  3 +--
 gramps/gen/db/__init__.py | 15 ---------------
 gramps/gen/dbstate.py     | 26 +++++++++++++++++++++++---
 gramps/gui/dbloader.py    |  3 +--
 gramps/gui/dbman.py       |  7 +++----
 7 files changed, 31 insertions(+), 31 deletions(-)

diff --git a/gramps/cli/arghandler.py b/gramps/cli/arghandler.py
index c7a5f5a4a..47e9b8da6 100644
--- a/gramps/cli/arghandler.py
+++ b/gramps/cli/arghandler.py
@@ -44,7 +44,6 @@ import sys
 #-------------------------------------------------------------------------
 from gramps.gen.recentfiles import recent_files
 from gramps.gen.utils.file import rm_tempdir, get_empty_tempdir
-from gramps.gen.db import make_database
 from .clidbman import CLIDbManager, NAME_FILE, find_locker_name
 
 from gramps.gen.plug import BasePluginManager
@@ -492,7 +491,7 @@ class ArgHandler(object):
                 else:
                     self.imp_db_path = get_empty_tempdir("import_dbdir")
 
-                    newdb = make_database("bsddb", self.dbstate)
+                    newdb = self.dbstate.make_database("bsddb")
                     newdb.write_version(self.imp_db_path)
                 
                 try:
diff --git a/gramps/cli/clidbman.py b/gramps/cli/clidbman.py
index 52b183013..e73bd71d6 100644
--- a/gramps/cli/clidbman.py
+++ b/gramps/cli/clidbman.py
@@ -54,7 +54,6 @@ _LOG = logging.getLogger(DBLOGNAME)
 #-------------------------------------------------------------------------
 from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
-from gramps.gen.db import make_database
 from gramps.gen.plug import BasePluginManager
 from gramps.gen.config import config
 from gramps.gen.constfunc import win, conv_to_unicode
@@ -295,7 +294,7 @@ class CLIDbManager(object):
         if create_db:
             # write the version number into metadata
             
-            newdb = make_database("bsddb", self.dbstate)
+            newdb = self.dbstate.make_database("bsddb")
             newdb.write_version(new_path)
 
         (tval, last) = time_val(new_path)
@@ -362,7 +361,7 @@ class CLIDbManager(object):
                 # Create a new database
                 self.__start_cursor(_("Importing data..."))
 
-                dbase = make_database("bsddb", self.dbstate)
+                dbase = self.dbstate.make_database("bsddb")
                 dbase.load(new_path, user.callback)
     
                 import_function = plugin.get_import_function()
diff --git a/gramps/cli/grampscli.py b/gramps/cli/grampscli.py
index 00b66d352..d1d3cfc07 100644
--- a/gramps/cli/grampscli.py
+++ b/gramps/cli/grampscli.py
@@ -49,7 +49,6 @@ from gramps.gen.config import config
 from gramps.gen.const import PLUGINS_DIR, USER_PLUGINS
 from gramps.gen.errors import DbError
 from gramps.gen.dbstate import DbState
-from gramps.gen.db import make_database
 from gramps.gen.db.exceptions import (DbUpgradeRequiredError, 
                                       BsddbDowngradeError, 
                                       DbVersionError, 
@@ -152,7 +151,7 @@ class CLIDbLoader(object):
         else:
             mode = 'w'
 
-        db = make_database("bsddb", self.dbstate)
+        db = self.dbstate.make_database("bsddb")
         
         self.dbstate.change_database(db)
         self.dbstate.db.disable_signals()
diff --git a/gramps/gen/db/__init__.py b/gramps/gen/db/__init__.py
index b977887b7..4ddaf5386 100644
--- a/gramps/gen/db/__init__.py
+++ b/gramps/gen/db/__init__.py
@@ -94,18 +94,3 @@ from .txn import *
 from .exceptions import *
 #from .write import *
 #from .backup import backup, restore
-
-def make_database(id, dbstate):
-    from gramps.cli.grampscli import CLIManager
-    from gramps.gen.plug import BasePluginManager
-    from gramps.cli.user import User
-
-    climanager = CLIManager(dbstate, setloader=False, user=User()) # do not load db_loader
-    climanager.do_reg_plugins(dbstate, None)
-    pmgr = BasePluginManager.get_instance()
-    pdata = pmgr.get_plugin(id)
-
-    mod = pmgr.load_plugin(pdata)
-    database = getattr(mod, pdata.databaseclass)
-    return database()
-
diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py
index 12ee68ea9..04a8e683d 100644
--- a/gramps/gen/dbstate.py
+++ b/gramps/gen/dbstate.py
@@ -24,7 +24,6 @@ Provide the database state class
 """
 
 from .db import DbReadBase
-from .db import make_database
 from .proxy.proxybase import ProxyDbBase
 from .utils.callback import Callback
 from .config import config
@@ -45,7 +44,7 @@ class DbState(Callback):
         just a place holder until a real DB is assigned.
         """
         Callback.__init__(self)
-        self.db      = make_database("bsddb", self)
+        self.db      = self.make_database("bsddb")
         self.open    = False
         self.stack = []
 
@@ -88,7 +87,7 @@ class DbState(Callback):
         """
         self.emit('no-database', ())
         self.db.close()
-        self.db = make_database("bsddb", self)
+        self.db = self.make_database("bsddb")
         self.db.db_is_open = False
         self.open = False
         self.emit('database-changed', (self.db, ))
@@ -122,3 +121,24 @@ class DbState(Callback):
         """
         self.db = self.stack.pop()
         self.emit('database-changed', (self.db, ))
+
+    def make_database(self, id):
+        """
+        Make a database, given a plugin id.
+        """
+        from .plug import BasePluginManager
+        from .const import PLUGINS_DIR, USER_PLUGINS
+
+        pmgr = BasePluginManager.get_instance()
+        pdata = pmgr.get_plugin(id)
+        
+        if not pdata:
+            # This might happen if using gramps from outside, and
+            # we haven't loaded plugins yet
+            pmgr.reg_plugins(PLUGINS_DIR, self, None)
+            pmgr.reg_plugins(USER_PLUGINS, self, None, load_on_reg=True)
+            pdata = pmgr.get_plugin(id)
+
+        mod = pmgr.load_plugin(pdata)
+        database = getattr(mod, pdata.databaseclass)
+        return database()
diff --git a/gramps/gui/dbloader.py b/gramps/gui/dbloader.py
index 18a1c3281..f3d303b15 100644
--- a/gramps/gui/dbloader.py
+++ b/gramps/gui/dbloader.py
@@ -55,7 +55,6 @@ from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
 from gramps.cli.grampscli import CLIDbLoader
 from gramps.gen.config import config
-from gramps.gen.db import make_database
 from gramps.gen.db.exceptions import (DbUpgradeRequiredError, 
                                       BsddbDowngradeError, 
                                       DbVersionError, 
@@ -305,7 +304,7 @@ class DbLoader(CLIDbLoader):
         else:
             mode = 'w'
 
-        db = make_database("bsddb", self.dbstate)
+        db = self.dbstate.make_database("bsddb")
         db.disable_signals()
         self.dbstate.no_database()
 
diff --git a/gramps/gui/dbman.py b/gramps/gui/dbman.py
index c5ccbe657..733bf9ecc 100644
--- a/gramps/gui/dbman.py
+++ b/gramps/gui/dbman.py
@@ -73,7 +73,6 @@ _ = glocale.translation.gettext
 from gramps.gen.const import URL_WIKISTRING
 from .user import User
 from .dialog import ErrorDialog, QuestionDialog, QuestionDialog2
-from gramps.gen.db import make_database
 from .pluginmanager import GuiPluginManager
 from gramps.cli.clidbman import CLIDbManager, NAME_FILE, time_val
 from .ddtargets import DdTargets
@@ -532,7 +531,7 @@ class DbManager(CLIDbManager):
         
         self.__start_cursor(_("Extracting archive..."))
 
-        dbase = make_database("bsddb", self.dbstate)
+        dbase = self.dbstate.make_database("bsddb")
         dbase.load(new_path, None)
         
         self.__start_cursor(_("Importing archive..."))
@@ -719,10 +718,10 @@ class DbManager(CLIDbManager):
                 fname = os.path.join(dirname, filename)
                 os.unlink(fname)
 
-        newdb = make_database("bsddb", self.dbstate)
+        newdb = self.dbstate.make_database("bsddb")
         newdb.write_version(dirname)
 
-        dbase = make_database("bsddb", self.dbstate)
+        dbase = self.dbstate.make_database("bsddb")
         dbase.set_save_path(dirname)
         dbase.load(dirname, None)
 

From e7d62cf9b19ed90e97c5386e4603892760b41dc5 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Tue, 12 May 2015 22:03:10 -0400
Subject: [PATCH 053/105] Only BSDDB plugin needs bsddb3; back/restore moved to
 db

---
 gramps/cli/clidbman.py                        |  53 +-----
 gramps/gen/db/__init__.py                     |   6 -
 gramps/gen/db/backup.py                       |   8 -
 gramps/gen/db/dbconst.py                      |  15 +-
 gramps/gen/plug/_gramplet.py                  |   6 -
 gramps/grampsapp.py                           |   8 -
 gramps/gui/aboutdialog.py                     |   9 +-
 gramps/gui/dbman.py                           |   3 +-
 gramps/gui/editors/editfamily.py              |  10 +-
 gramps/gui/logger/_errorreportassistant.py    |   9 +-
 gramps/gui/viewmanager.py                     |   3 +-
 .../database/bsddb_support/__init__.py        |   1 -
 .../plugins/database/bsddb_support/backup.py  | 139 ----------------
 .../plugins/database/bsddb_support/summary.py |  62 +++++++
 .../database/bsddb_support}/test/__init__.py  |   0
 .../bsddb_support}/test/cursor_test.py        |   0
 .../database/bsddb_support}/test/db_test.py   |   0
 .../bsddb_support}/test/grampsdbtestbase.py   |   0
 .../bsddb_support}/test/reference_map_test.py |   0
 .../plugins/database/bsddb_support/write.py   | 156 ++++++++++++++++--
 .../{bsddb_support => }/dictionary.py         |   0
 gramps/plugins/gramplet/leak.py               |   8 +-
 22 files changed, 239 insertions(+), 257 deletions(-)
 delete mode 100644 gramps/gen/db/backup.py
 create mode 100644 gramps/plugins/database/bsddb_support/summary.py
 rename gramps/{gen/db => plugins/database/bsddb_support}/test/__init__.py (100%)
 rename gramps/{gen/db => plugins/database/bsddb_support}/test/cursor_test.py (100%)
 rename gramps/{gen/db => plugins/database/bsddb_support}/test/db_test.py (100%)
 rename gramps/{gen/db => plugins/database/bsddb_support}/test/grampsdbtestbase.py (100%)
 rename gramps/{gen/db => plugins/database/bsddb_support}/test/reference_map_test.py (100%)
 rename gramps/plugins/database/{bsddb_support => }/dictionary.py (100%)

diff --git a/gramps/cli/clidbman.py b/gramps/cli/clidbman.py
index e73bd71d6..a4986f615 100644
--- a/gramps/cli/clidbman.py
+++ b/gramps/cli/clidbman.py
@@ -137,57 +137,8 @@ class CLIDbManager(object):
         current DB.
         Returns ("Unknown", "Unknown", "Unknown") if invalid DB or other error.
         """
-        from bsddb3 import dbshelve, db
-
-        from gramps.gen.db import META, PERSON_TBL
-        from  gramps.gen.db.dbconst import BDBVERSFN
-
-        bdbversion_file = os.path.join(dirpath, BDBVERSFN)
-        if os.path.isfile(bdbversion_file):
-            vers_file = open(bdbversion_file)
-            bsddb_version = vers_file.readline().strip()
-        else:
-            return "Unknown", "Unknown", "Unknown"
-        
-        current_bsddb_version = str(db.version())
-        if bsddb_version != current_bsddb_version:
-            return "Unknown", bsddb_version, "Unknown"
-        
-        env = db.DBEnv()
-        flags = db.DB_CREATE | db.DB_PRIVATE |\
-            db.DB_INIT_MPOOL |\
-            db.DB_INIT_LOG | db.DB_INIT_TXN
-        try:
-            env.open(dirpath, flags)
-        except Exception as msg:
-            LOG.warning("Error opening db environment for '%s': %s" %
-                        (name, str(msg)))
-            try:
-                env.close()
-            except Exception as msg:
-                LOG.warning("Error closing db environment for '%s': %s" %
-                        (name, str(msg)))
-            return "Unknown", bsddb_version, "Unknown"
-        dbmap1 = dbshelve.DBShelf(env)
-        fname = os.path.join(dirpath, META + ".db")
-        try:
-            dbmap1.open(fname, META, db.DB_HASH, db.DB_RDONLY)
-        except:
-            env.close()
-            return "Unknown", bsddb_version, "Unknown"
-        schema_version = dbmap1.get(b'version', default=None)
-        dbmap1.close()
-        dbmap2 = dbshelve.DBShelf(env)
-        fname = os.path.join(dirpath, PERSON_TBL + ".db")
-        try:
-            dbmap2.open(fname, PERSON_TBL, db.DB_HASH, db.DB_RDONLY)
-        except:
-            env.close()
-            return "Unknown", bsddb_version, schema_version
-        count = len(dbmap2)
-        dbmap2.close()
-        env.close()
-        return (count, bsddb_version, schema_version)
+        ## Maybe return the txt file contents, for now
+        return ("Unknown", "Unknown", "Unknown")
 
     def family_tree_summary(self):
         """
diff --git a/gramps/gen/db/__init__.py b/gramps/gen/db/__init__.py
index 4ddaf5386..066571e38 100644
--- a/gramps/gen/db/__init__.py
+++ b/gramps/gen/db/__init__.py
@@ -86,11 +86,5 @@ More details can be found in the manual's
 
 from .base import *
 from .dbconst import *
-#from .cursor import *
-#from .read import *
-#from .bsddbtxn import *
 from .txn import *
-#from .undoredo import *
 from .exceptions import *
-#from .write import *
-#from .backup import backup, restore
diff --git a/gramps/gen/db/backup.py b/gramps/gen/db/backup.py
deleted file mode 100644
index 52d4d5f05..000000000
--- a/gramps/gen/db/backup.py
+++ /dev/null
@@ -1,8 +0,0 @@
-import logging
-LOG = logging.getLogger(".Backup")
-
-def backup(database):
-    print("FIXME")
-
-def restore(database):
-    print("FIXME")
diff --git a/gramps/gen/db/dbconst.py b/gramps/gen/db/dbconst.py
index ba3514afb..7b91ab7e6 100644
--- a/gramps/gen/db/dbconst.py
+++ b/gramps/gen/db/dbconst.py
@@ -31,8 +31,7 @@ Declare constants used by database modules
 __all__ = (
             ('DBPAGE', 'DBMODE', 'DBCACHE', 'DBLOCKS', 'DBOBJECTS', 'DBUNDO',
              'DBEXT', 'DBMODE_R', 'DBMODE_W', 'DBUNDOFN', 'DBLOCKFN',
-             'DBRECOVFN','BDBVERSFN', 'DBLOGNAME', 'DBFLAGS_O',  'DBFLAGS_R',
-             'DBFLAGS_D', 'SCHVERSFN', 'PCKVERSFN'
+             'DBRECOVFN','BDBVERSFN', 'DBLOGNAME', 'SCHVERSFN', 'PCKVERSFN'
             ) +
             
             ('PERSON_KEY', 'FAMILY_KEY', 'SOURCE_KEY', 'CITATION_KEY',
@@ -60,18 +59,6 @@ DBLOCKS   = 100000          # Maximum number of locks supported
 DBOBJECTS = 100000          # Maximum number of simultaneously locked objects
 DBUNDO    = 1000            # Maximum size of undo buffer
 
-try:
-    from bsddb3.db import DB_CREATE, DB_AUTO_COMMIT, DB_DUP, DB_DUPSORT, DB_RDONLY
-    DBFLAGS_O = DB_CREATE | DB_AUTO_COMMIT  # Default flags for database open
-    DBFLAGS_R = DB_RDONLY                   # Flags to open a database read-only
-    DBFLAGS_D = DB_DUP | DB_DUPSORT         # Default flags for duplicate keys
-except:
-    print("WARNING: no bsddb support")
-    # FIXME: make this more abstract to deal with other backends, or do not import
-    DBFLAGS_O = DB_CREATE = DB_AUTO_COMMIT = 0
-    DBFLAGS_R = DB_RDONLY = 0
-    DBFLAGS_D = DB_DUP = DB_DUPSORT = 0
-
 PERSON_KEY     = 0
 FAMILY_KEY     = 1
 SOURCE_KEY     = 2
diff --git a/gramps/gen/plug/_gramplet.py b/gramps/gen/plug/_gramplet.py
index b812a7fb7..0159a8d03 100644
--- a/gramps/gen/plug/_gramplet.py
+++ b/gramps/gen/plug/_gramplet.py
@@ -320,8 +320,6 @@ class Gramplet(object):
             self._idle_id = 0
             LOG.debug("gramplet updater: %s : One time, done!" % self.gui.title)
             return False
-        # FIXME: find out why Data Entry has this error, or just ignore it
-        import bsddb3 as bsddb
         try:
             retval = next(self._generator)
             if not retval:
@@ -332,10 +330,6 @@ class Gramplet(object):
             LOG.debug("gramplet updater: %s: return %s" % 
                       (self.gui.title, retval))
             return retval
-        except bsddb.db.DBCursorClosedError:
-            # not sure why---caused by Data Entry Gramplet
-            LOG.warn("bsddb.db.DBCursorClosedError in: %s" % self.gui.title)
-            return False
         except StopIteration:
             self._idle_id = 0
             self._generator.close()
diff --git a/gramps/grampsapp.py b/gramps/grampsapp.py
index b9e4a6015..e2d7215e7 100644
--- a/gramps/grampsapp.py
+++ b/gramps/grampsapp.py
@@ -136,14 +136,6 @@ if not sys.version_info >= MIN_PYTHON_VERSION :
              'v3': MIN_PYTHON_VERSION[2]})
     sys.exit(1)
 
-try:
-    import bsddb3
-except ImportError:
-    logging.warning(_("\nYou don't have the python3 bsddb3 package installed."
-        " This package is needed to start Gramps.\n\n"
-         "Gramps will terminate now."))
-    sys.exit(1)
-    
 #-------------------------------------------------------------------------
 #
 # Gramps libraries
diff --git a/gramps/gui/aboutdialog.py b/gramps/gui/aboutdialog.py
index 9bd977d55..9886b550f 100644
--- a/gramps/gui/aboutdialog.py
+++ b/gramps/gui/aboutdialog.py
@@ -28,7 +28,12 @@
 import os
 import sys
 import io
-import bsddb3 as bsddb
+
+try:
+    import bsddb3 as bsddb ## ok, in try/except
+    BSDDB_STR = ellipses(str(bsddb.__version__) + " " + str(bsddb.db.version()))
+except:
+    BSDDB_STR = 'not found'
 
 ##import logging
 ##_LOG = logging.getLogger(".GrampsAboutDialog")
@@ -125,7 +130,7 @@ class GrampsAboutDialog(Gtk.AboutDialog):
                  "Distribution: %s")
                 % (ellipses(str(VERSION)),
                    ellipses(str(sys.version).replace('\n','')),
-                   ellipses(str(bsddb.__version__) + " " + str(bsddb.db.version())),
+                   BSDDB_STR,
                    ellipses(get_env_var('LANG','')),
                    ellipses(operatingsystem),
                    ellipses(distribution)))
diff --git a/gramps/gui/dbman.py b/gramps/gui/dbman.py
index 733bf9ecc..b105ac37b 100644
--- a/gramps/gui/dbman.py
+++ b/gramps/gui/dbman.py
@@ -78,7 +78,6 @@ from gramps.cli.clidbman import CLIDbManager, NAME_FILE, time_val
 from .ddtargets import DdTargets
 from gramps.gen.recentfiles import rename_filename, remove_filename
 from .glade import Glade
-from gramps.gen.db.backup import restore
 from gramps.gen.db.exceptions import DbException
 
 
@@ -728,7 +727,7 @@ class DbManager(CLIDbManager):
         self.__start_cursor(_("Rebuilding database from backup files"))
         
         try:
-            restore(dbase)
+            dbase.restore()
         except DbException as msg:
             DbManager.ERROR(_("Error restoring backup data"), msg)
 
diff --git a/gramps/gui/editors/editfamily.py b/gramps/gui/editors/editfamily.py
index 707bf0853..2c5110b1b 100644
--- a/gramps/gui/editors/editfamily.py
+++ b/gramps/gui/editors/editfamily.py
@@ -26,7 +26,6 @@
 # python modules
 #
 #-------------------------------------------------------------------------
-from bsddb3 import db as bsddb_db
 import pickle
 
 #-------------------------------------------------------------------------
@@ -1026,10 +1025,11 @@ class EditFamily(EditPrimary):
                )
             
     def save(self, *obj):
-        try:
-            self.__do_save()
-        except bsddb_db.DBRunRecoveryError as msg:
-            RunDatabaseRepair(msg[1])
+        ## FIXME: how to catch a specific error?
+        #try:
+        self.__do_save()
+        #except bsddb_db.DBRunRecoveryError as msg:
+        #    RunDatabaseRepair(msg[1])
 
     def __do_save(self):
         self.ok_button.set_sensitive(False)
diff --git a/gramps/gui/logger/_errorreportassistant.py b/gramps/gui/logger/_errorreportassistant.py
index e5c81597a..14c6ba1a2 100644
--- a/gramps/gui/logger/_errorreportassistant.py
+++ b/gramps/gui/logger/_errorreportassistant.py
@@ -30,7 +30,12 @@ from gi.repository import GdkPixbuf
 from gi.repository import GObject
 import cairo
 import sys, os
-import bsddb3 as bsddb
+
+try:
+    import bsddb3 as bsddb # ok, in try/except
+    BSDDB_STR = str(bsddb.__version__) + " " + str(bsddb.db.version())
+except:
+    BSDDB_STR = 'not found'
     
 #-------------------------------------------------------------------------
 #
@@ -166,7 +171,7 @@ class ErrorReportAssistant(Gtk.Assistant):
                "gobject version: %s\n"\
                "cairo version  : %s"\
                % (str(sys.version).replace('\n',''),
-                  str(bsddb.__version__) + " " + str(bsddb.db.version()),
+                  BSDDB_STR,
                   str(VERSION),
                   get_env_var('LANG',''),
                   operatingsystem,
diff --git a/gramps/gui/viewmanager.py b/gramps/gui/viewmanager.py
index 721706faf..cb2be6f16 100644
--- a/gramps/gui/viewmanager.py
+++ b/gramps/gui/viewmanager.py
@@ -87,7 +87,6 @@ from gramps.gen.utils.file import media_path_full
 from .dbloader import DbLoader
 from .display import display_help, display_url
 from .configure import GrampsPreferences
-from gramps.gen.db.backup import backup
 from gramps.gen.db.exceptions import DbException
 from .aboutdialog import GrampsAboutDialog
 from .navigator import Navigator
@@ -762,7 +761,7 @@ class ViewManager(CLIManager):
             self.uistate.progress.show()
             self.uistate.push_message(self.dbstate, _("Autobackup..."))
             try:
-                backup(self.dbstate.db)
+                self.dbstate.db.backup()
             except DbException as msg:
                 ErrorDialog(_("Error saving backup data"), msg)
             self.uistate.set_busy_cursor(False)
diff --git a/gramps/plugins/database/bsddb_support/__init__.py b/gramps/plugins/database/bsddb_support/__init__.py
index 524ecb660..1dd4bce56 100644
--- a/gramps/plugins/database/bsddb_support/__init__.py
+++ b/gramps/plugins/database/bsddb_support/__init__.py
@@ -93,4 +93,3 @@ from gramps.gen.db.txn import *
 from .undoredo import *
 from gramps.gen.db.exceptions import *
 from .write import *
-from .backup import backup, restore
diff --git a/gramps/plugins/database/bsddb_support/backup.py b/gramps/plugins/database/bsddb_support/backup.py
index 29dd46c54..94350da56 100644
--- a/gramps/plugins/database/bsddb_support/backup.py
+++ b/gramps/plugins/database/bsddb_support/backup.py
@@ -72,142 +72,3 @@ from .write import FAMILY_TBL, PLACES_TBL, SOURCES_TBL, MEDIA_TBL, \
 import logging
 LOG = logging.getLogger(".Backup")
 
-def backup(database):
-    """
-    Exports the database to a set of backup files. These files consist
-    of the pickled database tables, one file for each table.
-
-    The heavy lifting is done by the private :py:func:`__do__export` function.
-    The purpose of this function is to catch any exceptions that occur.
-
-    :param database: database instance to backup
-    :type database: DbDir
-    """
-    try:
-        __do_export(database)
-    except (OSError, IOError) as msg:
-        raise DbException(str(msg))
-
-def __mk_backup_name(database, base):
-    """
-    Return the backup name of the database table
-
-    :param database: database instance 
-    :type database: DbDir
-    :param base: base name of the table
-    :type base: str
-    """
-    return os.path.join(database.get_save_path(), base + ".gbkp")
-
-def __mk_tmp_name(database, base):
-    """
-    Return the temporary backup name of the database table
-
-    :param database: database instance 
-    :type database: DbDir
-    :param base: base name of the table
-    :type base: str
-    """
-    return os.path.join(database.get_save_path(), base + ".gbkp.new")
-
-def __do_export(database):
-    """
-    Loop through each table of the database, saving the pickled data
-    a file.
-
-    :param database: database instance to backup
-    :type database: DbDir
-    """
-    try:
-        for (base, tbl) in __build_tbl_map(database):
-            backup_name = __mk_tmp_name(database, base)
-            backup_table = open(backup_name, 'wb')
-    
-            cursor = tbl.cursor()
-            data = cursor.first()
-            while data:
-                pickle.dump(data, backup_table, 2)
-                data = cursor.next()
-            cursor.close()
-            backup_table.close()
-    except (IOError,OSError):
-        return
-
-    for (base, tbl) in __build_tbl_map(database):
-        new_name = __mk_backup_name(database, base)
-        old_name = __mk_tmp_name(database, base)
-        if os.path.isfile(new_name):
-            os.unlink(new_name)
-        os.rename(old_name, new_name)
-
-def restore(database):
-    """
-    Restores the database to a set of backup files. These files consist
-    of the pickled database tables, one file for each table.
-
-    The heavy lifting is done by the private :py:func:`__do__restore` function.
-    The purpose of this function is to catch any exceptions that occur.
-
-    :param database: database instance to restore
-    :type database: DbDir
-    """
-    try:
-        __do_restore(database)
-    except (OSError, IOError) as msg:
-        raise DbException(str(msg))
-
-def __do_restore(database):
-    """
-    Loop through each table of the database, restoring the pickled data
-    to the appropriate database file.
-
-    :param database: database instance to backup
-    :type database: DbDir
-    """
-    for (base, tbl) in __build_tbl_map(database):
-        backup_name = __mk_backup_name(database, base)
-        backup_table = open(backup_name, 'rb')
-        __load_tbl_txn(database, backup_table, tbl)
-
-    database.rebuild_secondary()
-
-def __load_tbl_txn(database, backup_table, tbl):
-    """
-    Return the temporary backup name of the database table
-
-    :param database: database instance 
-    :type database: DbDir
-    :param backup_table: file containing the backup data
-    :type backup_table: file
-    :param tbl: Berkeley db database table
-    :type tbl: Berkeley db database table
-    """
-    try:
-        while True:
-            data = pickle.load(backup_table)
-            txn = database.env.txn_begin()
-            tbl.put(data[0], data[1], txn=txn)
-            txn.commit()
-    except EOFError:
-        backup_table.close()
-
-def __build_tbl_map(database):
-    """
-    Builds a table map of names to database tables.
-
-    :param database: database instance to backup
-    :type database: DbDir
-    """
-    return [
-        ( PERSON_TBL,  database.person_map.db),
-        ( FAMILY_TBL,  database.family_map.db),
-        ( PLACES_TBL,  database.place_map.db),
-        ( SOURCES_TBL, database.source_map.db),
-        ( CITATIONS_TBL, database.citation_map.db),
-        ( REPO_TBL,    database.repository_map.db),
-        ( NOTE_TBL,    database.note_map.db),
-        ( MEDIA_TBL,   database.media_map.db),
-        ( EVENTS_TBL,  database.event_map.db),
-        ( TAG_TBL,     database.tag_map.db),
-        ( META,        database.metadata.db),
-        ]
diff --git a/gramps/plugins/database/bsddb_support/summary.py b/gramps/plugins/database/bsddb_support/summary.py
new file mode 100644
index 000000000..ccc94d2dc
--- /dev/null
+++ b/gramps/plugins/database/bsddb_support/summary.py
@@ -0,0 +1,62 @@
+## Removed from clidbman.py
+## specific to bsddb
+
+from bsddb3 import dbshelve, db
+import os
+
+from gramps.gen.db import META, PERSON_TBL
+from  gramps.gen.db.dbconst import BDBVERSFN
+
+def get_dbdir_summary(dirpath, name):
+    """
+    Returns (people_count, bsddb_version, schema_version) of
+    current DB.
+    Returns ("Unknown", "Unknown", "Unknown") if invalid DB or other error.
+    """
+
+    bdbversion_file = os.path.join(dirpath, BDBVERSFN)
+    if os.path.isfile(bdbversion_file):
+        vers_file = open(bdbversion_file)
+        bsddb_version = vers_file.readline().strip()
+    else:
+        return "Unknown", "Unknown", "Unknown"
+        
+    current_bsddb_version = str(db.version())
+    if bsddb_version != current_bsddb_version:
+        return "Unknown", bsddb_version, "Unknown"
+        
+    env = db.DBEnv()
+    flags = db.DB_CREATE | db.DB_PRIVATE |\
+        db.DB_INIT_MPOOL |\
+        db.DB_INIT_LOG | db.DB_INIT_TXN
+    try:
+        env.open(dirpath, flags)
+    except Exception as msg:
+        LOG.warning("Error opening db environment for '%s': %s" %
+                    (name, str(msg)))
+        try:
+            env.close()
+        except Exception as msg:
+            LOG.warning("Error closing db environment for '%s': %s" %
+                    (name, str(msg)))
+        return "Unknown", bsddb_version, "Unknown"
+    dbmap1 = dbshelve.DBShelf(env)
+    fname = os.path.join(dirpath, META + ".db")
+    try:
+        dbmap1.open(fname, META, db.DB_HASH, db.DB_RDONLY)
+    except:
+        env.close()
+        return "Unknown", bsddb_version, "Unknown"
+    schema_version = dbmap1.get(b'version', default=None)
+    dbmap1.close()
+    dbmap2 = dbshelve.DBShelf(env)
+    fname = os.path.join(dirpath, PERSON_TBL + ".db")
+    try:
+        dbmap2.open(fname, PERSON_TBL, db.DB_HASH, db.DB_RDONLY)
+    except:
+        env.close()
+        return "Unknown", bsddb_version, schema_version
+    count = len(dbmap2)
+    dbmap2.close()
+    env.close()
+    return (count, bsddb_version, schema_version)
diff --git a/gramps/gen/db/test/__init__.py b/gramps/plugins/database/bsddb_support/test/__init__.py
similarity index 100%
rename from gramps/gen/db/test/__init__.py
rename to gramps/plugins/database/bsddb_support/test/__init__.py
diff --git a/gramps/gen/db/test/cursor_test.py b/gramps/plugins/database/bsddb_support/test/cursor_test.py
similarity index 100%
rename from gramps/gen/db/test/cursor_test.py
rename to gramps/plugins/database/bsddb_support/test/cursor_test.py
diff --git a/gramps/gen/db/test/db_test.py b/gramps/plugins/database/bsddb_support/test/db_test.py
similarity index 100%
rename from gramps/gen/db/test/db_test.py
rename to gramps/plugins/database/bsddb_support/test/db_test.py
diff --git a/gramps/gen/db/test/grampsdbtestbase.py b/gramps/plugins/database/bsddb_support/test/grampsdbtestbase.py
similarity index 100%
rename from gramps/gen/db/test/grampsdbtestbase.py
rename to gramps/plugins/database/bsddb_support/test/grampsdbtestbase.py
diff --git a/gramps/gen/db/test/reference_map_test.py b/gramps/plugins/database/bsddb_support/test/reference_map_test.py
similarity index 100%
rename from gramps/gen/db/test/reference_map_test.py
rename to gramps/plugins/database/bsddb_support/test/reference_map_test.py
diff --git a/gramps/plugins/database/bsddb_support/write.py b/gramps/plugins/database/bsddb_support/write.py
index 768db02d9..2b5648b9d 100644
--- a/gramps/plugins/database/bsddb_support/write.py
+++ b/gramps/plugins/database/bsddb_support/write.py
@@ -40,16 +40,12 @@ from functools import wraps
 import logging
 from sys import maxsize, getfilesystemencoding, version_info
 
-try:
-    from bsddb3 import dbshelve, db
-except:
-    # FIXME: make this more abstract to deal with other backends
-    class db:
-        DB_HASH = 0
-        DBRunRecoveryError = 0
-        DBAccessError = 0
-        DBPageNotFoundError = 0
-        DBInvalidArgError = 0
+from bsddb3 import dbshelve, db
+from bsddb3.db import DB_CREATE, DB_AUTO_COMMIT, DB_DUP, DB_DUPSORT, DB_RDONLY
+
+DBFLAGS_O = DB_CREATE | DB_AUTO_COMMIT  # Default flags for database open
+DBFLAGS_R = DB_RDONLY                   # Flags to open a database read-only
+DBFLAGS_D = DB_DUP | DB_DUPSORT         # Default flags for duplicate keys
 
 #-------------------------------------------------------------------------
 #
@@ -2450,6 +2446,146 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
         """
         return DbTxn
 
+    def backup(self):
+        """
+        Exports the database to a set of backup files. These files consist
+        of the pickled database tables, one file for each table.
+        
+        The heavy lifting is done by the private :py:func:`__do__export` function.
+        The purpose of this function is to catch any exceptions that occur.
+        
+        :param database: database instance to backup
+        :type database: DbDir
+        """
+        try:
+            do_export(self)
+        except (OSError, IOError) as msg:
+            raise DbException(str(msg))
+
+    def restore(self):
+        """
+        Restores the database to a set of backup files. These files consist
+        of the pickled database tables, one file for each table.
+        
+        The heavy lifting is done by the private :py:func:`__do__restore` function.
+        The purpose of this function is to catch any exceptions that occur.
+        
+        :param database: database instance to restore
+        :type database: DbDir
+        """
+        try:
+            do_restore(self)
+        except (OSError, IOError) as msg:
+            raise DbException(str(msg))
+
+def mk_backup_name(database, base):
+    """
+    Return the backup name of the database table
+
+    :param database: database instance 
+    :type database: DbDir
+    :param base: base name of the table
+    :type base: str
+    """
+    return os.path.join(database.get_save_path(), base + ".gbkp")
+
+def mk_tmp_name(database, base):
+    """
+    Return the temporary backup name of the database table
+
+    :param database: database instance 
+    :type database: DbDir
+    :param base: base name of the table
+    :type base: str
+    """
+    return os.path.join(database.get_save_path(), base + ".gbkp.new")
+
+def do_export(database):
+    """
+    Loop through each table of the database, saving the pickled data
+    a file.
+
+    :param database: database instance to backup
+    :type database: DbDir
+    """
+    try:
+        for (base, tbl) in build_tbl_map(database):
+            backup_name = mk_tmp_name(database, base)
+            backup_table = open(backup_name, 'wb')
+    
+            cursor = tbl.cursor()
+            data = cursor.first()
+            while data:
+                pickle.dump(data, backup_table, 2)
+                data = cursor.next()
+            cursor.close()
+            backup_table.close()
+    except (IOError,OSError):
+        return
+
+    for (base, tbl) in build_tbl_map(database):
+        new_name = mk_backup_name(database, base)
+        old_name = mk_tmp_name(database, base)
+        if os.path.isfile(new_name):
+            os.unlink(new_name)
+        os.rename(old_name, new_name)
+
+def do_restore(database):
+    """
+    Loop through each table of the database, restoring the pickled data
+    to the appropriate database file.
+
+    :param database: database instance to backup
+    :type database: DbDir
+    """
+    for (base, tbl) in build_tbl_map(database):
+        backup_name = mk_backup_name(database, base)
+        backup_table = open(backup_name, 'rb')
+        load_tbl_txn(database, backup_table, tbl)
+
+    database.rebuild_secondary()
+
+def load_tbl_txn(database, backup_table, tbl):
+    """
+    Return the temporary backup name of the database table
+
+    :param database: database instance 
+    :type database: DbDir
+    :param backup_table: file containing the backup data
+    :type backup_table: file
+    :param tbl: Berkeley db database table
+    :type tbl: Berkeley db database table
+    """
+    try:
+        while True:
+            data = pickle.load(backup_table)
+            txn = database.env.txn_begin()
+            tbl.put(data[0], data[1], txn=txn)
+            txn.commit()
+    except EOFError:
+        backup_table.close()
+
+def build_tbl_map(database):
+    """
+    Builds a table map of names to database tables.
+
+    :param database: database instance to backup
+    :type database: DbDir
+    """
+    return [
+        ( PERSON_TBL,  database.person_map.db),
+        ( FAMILY_TBL,  database.family_map.db),
+        ( PLACES_TBL,  database.place_map.db),
+        ( SOURCES_TBL, database.source_map.db),
+        ( CITATIONS_TBL, database.citation_map.db),
+        ( REPO_TBL,    database.repository_map.db),
+        ( NOTE_TBL,    database.note_map.db),
+        ( MEDIA_TBL,   database.media_map.db),
+        ( EVENTS_TBL,  database.event_map.db),
+        ( TAG_TBL,     database.tag_map.db),
+        ( META,        database.metadata.db),
+        ]
+
 def _mkname(path, name):
     return os.path.join(path, name + DBEXT)
 
diff --git a/gramps/plugins/database/bsddb_support/dictionary.py b/gramps/plugins/database/dictionary.py
similarity index 100%
rename from gramps/plugins/database/bsddb_support/dictionary.py
rename to gramps/plugins/database/dictionary.py
diff --git a/gramps/plugins/gramplet/leak.py b/gramps/plugins/gramplet/leak.py
index 2e30e7582..3c10a1681 100644
--- a/gramps/plugins/gramplet/leak.py
+++ b/gramps/plugins/gramplet/leak.py
@@ -31,7 +31,6 @@ Show uncollected objects in a window.
 #------------------------------------------------------------------------
 from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
-from bsddb3.db import DBError
 
 #------------------------------------------------------------------------
 #
@@ -155,6 +154,13 @@ class Leak(Gramplet):
                         parent=self.uistate.window)
 
     def display(self):
+        try:
+            from bsddb3.db import DBError
+        except:
+            class DBError(Exception):
+                """
+                Dummy.
+                """
         gc.collect(2)
         self.model.clear()
         count = 0

From c0f9559f8cd336d520ae9f90942f879a6c2d1687 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Tue, 12 May 2015 23:08:54 -0400
Subject: [PATCH 054/105] Database backend writes its plugin id in database.txt

---
 gramps/gen/db/dbconst.py                       | 4 +++-
 gramps/plugins/database/bsddb_support/write.py | 5 +++++
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/gramps/gen/db/dbconst.py b/gramps/gen/db/dbconst.py
index 7b91ab7e6..e53535248 100644
--- a/gramps/gen/db/dbconst.py
+++ b/gramps/gen/db/dbconst.py
@@ -31,7 +31,8 @@ Declare constants used by database modules
 __all__ = (
             ('DBPAGE', 'DBMODE', 'DBCACHE', 'DBLOCKS', 'DBOBJECTS', 'DBUNDO',
              'DBEXT', 'DBMODE_R', 'DBMODE_W', 'DBUNDOFN', 'DBLOCKFN',
-             'DBRECOVFN','BDBVERSFN', 'DBLOGNAME', 'SCHVERSFN', 'PCKVERSFN'
+             'DBRECOVFN','BDBVERSFN', 'DBLOGNAME', 'SCHVERSFN', 'PCKVERSFN',
+             'DBBACKEND'
             ) +
             
             ('PERSON_KEY', 'FAMILY_KEY', 'SOURCE_KEY', 'CITATION_KEY',
@@ -47,6 +48,7 @@ DBUNDOFN  = "undo.db"       # File name of 'undo' database
 DBLOCKFN  = "lock"          # File name of lock file
 DBRECOVFN = "need_recover"  # File name of recovery file
 BDBVERSFN = "bdbversion.txt"# File name of Berkeley DB version file
+DBBACKEND = "database.txt"  # File name of Database backend file
 SCHVERSFN = "schemaversion.txt"# File name of schema version file
 PCKVERSFN = "pickleupgrade.txt" # Indicator that pickle has been upgrade t Python3
 DBLOGNAME = ".Db"           # Name of logger
diff --git a/gramps/plugins/database/bsddb_support/write.py b/gramps/plugins/database/bsddb_support/write.py
index 2b5648b9d..606f9484a 100644
--- a/gramps/plugins/database/bsddb_support/write.py
+++ b/gramps/plugins/database/bsddb_support/write.py
@@ -2430,6 +2430,11 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
             version = str(_DBVERSION)
             version_file.write(version)
 
+        versionpath = os.path.join(name, str(DBBACKEND))
+        _LOG.debug("Write database backend file to 'bsddb'")
+        with open(versionpath, "w") as version_file:
+            version_file.write("bsddb")
+
         self.metadata.close()
         self.env.close()
   

From 337ba6b22a6ceb591b6d4fcd99e8e4ec667eaba3 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Wed, 13 May 2015 08:09:30 -0400
Subject: [PATCH 055/105] Added Django and Dictionary plugins, to be developed

---
 gramps/plugins/database/dbdjango.py           | 2036 ++++++++++++++++
 gramps/plugins/database/dictionarydb.gpr.py   |   31 +
 .../{dictionary.py => dictionarydb.py}        |   22 +-
 .../database/django_support/libdjango.py      | 2063 +++++++++++++++++
 gramps/plugins/database/djangodb.gpr.py       |   31 +
 gramps/plugins/database/djangodb.py           | 2036 ++++++++++++++++
 6 files changed, 6208 insertions(+), 11 deletions(-)
 create mode 100644 gramps/plugins/database/dbdjango.py
 create mode 100644 gramps/plugins/database/dictionarydb.gpr.py
 rename gramps/plugins/database/{dictionary.py => dictionarydb.py} (98%)
 create mode 100644 gramps/plugins/database/django_support/libdjango.py
 create mode 100644 gramps/plugins/database/djangodb.gpr.py
 create mode 100644 gramps/plugins/database/djangodb.py

diff --git a/gramps/plugins/database/dbdjango.py b/gramps/plugins/database/dbdjango.py
new file mode 100644
index 000000000..adb28dc54
--- /dev/null
+++ b/gramps/plugins/database/dbdjango.py
@@ -0,0 +1,2036 @@
+# Gramps - a GTK+/GNOME based genealogy program
+#
+# Copyright (C) 2009         Douglas S. Blank <doug.blank@gmail.com>
+#
+# 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+""" Implements a Db interface """
+
+#------------------------------------------------------------------------
+#
+# Python Modules
+#
+#------------------------------------------------------------------------
+import sys
+import time
+import re
+import base64
+import pickle
+import os
+
+#------------------------------------------------------------------------
+#
+# Gramps Modules
+#
+#------------------------------------------------------------------------
+import gramps
+from gramps.gen.lib import (Person, Family, Event, Place, Repository, 
+                            Citation, Source, Note, MediaObject, Tag, 
+                            Researcher)
+from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn
+from gramps.gen.utils.callback import Callback
+from gramps.gen.updatecallback import UpdateCallback
+from gramps.gen.db import (PERSON_KEY,
+                    FAMILY_KEY,
+                    CITATION_KEY,
+                    SOURCE_KEY,
+                    EVENT_KEY,
+                    MEDIA_KEY,
+                    PLACE_KEY,
+                    REPOSITORY_KEY,
+                    NOTE_KEY)
+from gramps.gen.utils.id import create_id
+from django.db import transaction
+
+class Environment(object):
+    """
+    Implements the Environment API.
+    """
+    def __init__(self, db):
+        self.db = db
+
+    def txn_begin(self):
+        return DjangoTxn("DbDjango Transaction", self.db)
+
+class Table(object):
+    """
+    Implements Table interface.
+    """
+    def __init__(self, funcs):
+        self.funcs = funcs
+
+    def cursor(self):
+        """
+        Returns a Cursor for this Table.
+        """
+        return self.funcs["cursor_func"]()
+
+    def put(key, data, txn=None):
+        self[key] = data
+
+class Map(dict):
+    """
+    Implements the map API for person_map, etc.
+    
+    Takes a Table() as argument.
+    """
+    def __init__(self, tbl, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.db = tbl
+
+class MetaCursor(object):
+    def __init__(self):
+        pass
+    def __enter__(self):
+        return self
+    def __iter__(self):
+        return self.__next__()
+    def __next__(self):
+        yield None
+    def __exit__(self, *args, **kwargs):
+        pass
+    def iter(self):
+        yield None
+    def first(self):
+        self._iter = self.__iter__()
+        return self.next()
+    def next(self):
+        try:
+            return next(self._iter)
+        except:
+            return None
+    def close(self):
+        pass
+
+class Cursor(object):
+    def __init__(self, model, func):
+        self.model = model
+        self.func = func
+    def __enter__(self):
+        return self
+    def __iter__(self):
+        return self.__next__()
+    def __next__(self):
+        for item in self.model.all():
+            yield (bytes(item.handle, "utf-8"), self.func(item.handle))
+    def __exit__(self, *args, **kwargs):
+        pass
+    def iter(self):
+        for item in self.model.all():
+            yield (bytes(item.handle, "utf-8"), self.func(item.handle))
+        yield None
+    def first(self):
+        self._iter = self.__iter__()
+        return self.next()
+    def next(self):
+        try:
+            return next(self._iter)
+        except:
+            return None
+    def close(self):
+        pass
+
+class Bookmarks(object):
+    def __init__(self):
+        self.handles = []
+    def get(self):
+        return self.handles
+    def append(self, handle):
+        self.handles.append(handle)
+
+class DjangoTxn(DbTxn):
+    def __init__(self, message, db, table=None):
+        DbTxn.__init__(self, message, db)
+        self.table = table
+
+    def get(self, key, default=None, txn=None, **kwargs):
+        """
+        Returns the data object associated with key
+        """
+        try:
+            return self.table.objects(handle=key)
+        except:
+            if txn and key in txn:
+                return txn[key]
+            else:
+                return None
+
+    def put(self, handle, new_data, txn):
+        """
+        """
+        txn[handle] = new_data
+
+    def commit(self):
+        pass
+
+class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
+    """
+    A Gramps Database Backend. This replicates the grampsdb functions.
+    """
+    # Set up dictionary for callback signal handler
+    # ---------------------------------------------
+    # 1. Signals for primary objects
+    __signals__ = dict((obj+'-'+op, signal)
+                       for obj in
+                       ['person', 'family', 'event', 'place',
+                        'source', 'citation', 'media', 'note', 'repository', 'tag']
+                       for op, signal in zip(
+                               ['add',   'update', 'delete', 'rebuild'],
+                               [(list,), (list,),  (list,),   None]
+                       )
+                   )
+    
+    # 2. Signals for long operations
+    __signals__.update(('long-op-'+op, signal) for op, signal in zip(
+        ['start',  'heartbeat', 'end'],
+        [(object,), None,       None]
+        ))
+
+    # 3. Special signal for change in home person
+    __signals__['home-person-changed'] = None
+
+    # 4. Signal for change in person group name, parameters are
+    __signals__['person-groupname-rebuild'] = (str, str)
+
+    def __init__(self, directory=None):
+        DbReadBase.__init__(self)
+        DbWriteBase.__init__(self)
+        Callback.__init__(self)
+        self._tables = {
+            'Person':
+                {
+                "handle_func": self.get_person_from_handle, 
+                "gramps_id_func": self.get_person_from_gramps_id,
+                "class_func": gramps.gen.lib.Person,
+                "cursor_func": self.get_person_cursor,
+                "handles_func": self.get_person_handles,
+                "iter_func": self.iter_people,
+                },
+            'Family':
+                {
+                "handle_func": self.get_family_from_handle, 
+                "gramps_id_func": self.get_family_from_gramps_id,
+                "class_func": gramps.gen.lib.Family,
+                "cursor_func": self.get_family_cursor,
+                "handles_func": self.get_family_handles,
+                "iter_func": self.iter_families,
+                },
+            'Source':
+                {
+                "handle_func": self.get_source_from_handle, 
+                "gramps_id_func": self.get_source_from_gramps_id,
+                "class_func": gramps.gen.lib.Source,
+                "cursor_func": self.get_source_cursor,
+                "handles_func": self.get_source_handles,
+                "iter_func": self.iter_sources,
+                },
+            'Citation':
+                {
+                "handle_func": self.get_citation_from_handle, 
+                "gramps_id_func": self.get_citation_from_gramps_id,
+                "class_func": gramps.gen.lib.Citation,
+                "cursor_func": self.get_citation_cursor,
+                "handles_func": self.get_citation_handles,
+                "iter_func": self.iter_citations,
+                },
+            'Event':
+                {
+                "handle_func": self.get_event_from_handle, 
+                "gramps_id_func": self.get_event_from_gramps_id,
+                "class_func": gramps.gen.lib.Event,
+                "cursor_func": self.get_event_cursor,
+                "handles_func": self.get_event_handles,
+                "iter_func": self.iter_events,
+                },
+            'Media':
+                {
+                "handle_func": self.get_object_from_handle, 
+                "gramps_id_func": self.get_object_from_gramps_id,
+                "class_func": gramps.gen.lib.MediaObject,
+                "cursor_func": self.get_media_cursor,
+                "handles_func": self.get_media_object_handles,
+                "iter_func": self.iter_media_objects,
+                },
+            'Place':
+                {
+                "handle_func": self.get_place_from_handle, 
+                "gramps_id_func": self.get_place_from_gramps_id,
+                "class_func": gramps.gen.lib.Place,
+                "cursor_func": self.get_place_cursor,
+                "handles_func": self.get_place_handles,
+                "iter_func": self.iter_places,
+                },
+            'Repository':
+                {
+                "handle_func": self.get_repository_from_handle, 
+                "gramps_id_func": self.get_repository_from_gramps_id,
+                "class_func": gramps.gen.lib.Repository,
+                "cursor_func": self.get_repository_cursor,
+                "handles_func": self.get_repository_handles,
+                "iter_func": self.iter_repositories,
+                },
+            'Note':
+                {
+                "handle_func": self.get_note_from_handle, 
+                "gramps_id_func": self.get_note_from_gramps_id,
+                "class_func": gramps.gen.lib.Note,
+                "cursor_func": self.get_note_cursor,
+                "handles_func": self.get_note_handles,
+                "iter_func": self.iter_notes,
+                },
+            'Tag':
+                {
+                "handle_func": self.get_tag_from_handle, 
+                "gramps_id_func": None,
+                "class_func": gramps.gen.lib.Tag,
+                "cursor_func": self.get_tag_cursor,
+                "handles_func": self.get_tag_handles,
+                "iter_func": self.iter_tags,
+                },
+            }
+        # skip GEDCOM cross-ref check for now:
+        self.set_feature("skip-check-xref", True)
+        self.readonly = False
+        self.db_is_open = True
+        self.name_formats = []
+        self.bookmarks = Bookmarks()
+        self.undo_callback = None
+        self.redo_callback = None
+        self.undo_history_callback = None
+        self.modified   = 0
+        self.txn = DjangoTxn("DbDjango Transaction", self)
+        self.transaction = None
+        # Import cache for gedcom import, uses transactions, and
+        # two step adding of objects.
+        self.import_cache = {}
+        self.use_import_cache = False
+        self.use_db_cache = True
+        self._directory = directory
+        if directory:
+            self.load(directory)
+
+    def load(self, directory, pulse_progress=None, mode=None):
+        self._directory = directory
+        from django.conf import settings
+        default_settings = {}
+        settings_file = os.path.join(directory, "default_settings.py")
+        with open(settings_file) as f:
+            code = compile(f.read(), settings_file, 'exec')
+            exec(code, globals(), default_settings)
+
+        class Module(object):
+            def __init__(self, dictionary):
+                self.dictionary = dictionary
+            def __getattr__(self, item):
+                return self.dictionary[item]
+
+        try:
+            settings.configure(Module(default_settings))
+        except RuntimeError:
+            # already configured; ignore
+            pass
+
+        import django
+        django.setup()
+
+        from django_support.libdjango import DjangoInterface
+        self.dji = DjangoInterface()
+        self.family_bookmarks = Bookmarks()
+        self.event_bookmarks = Bookmarks()
+        self.place_bookmarks = Bookmarks()
+        self.citation_bookmarks = Bookmarks()
+        self.source_bookmarks = Bookmarks()
+        self.repo_bookmarks = Bookmarks()
+        self.media_bookmarks = Bookmarks()
+        self.note_bookmarks = Bookmarks()
+        self.set_person_id_prefix('I%04d')
+        self.set_object_id_prefix('O%04d')
+        self.set_family_id_prefix('F%04d')
+        self.set_citation_id_prefix('C%04d')
+        self.set_source_id_prefix('S%04d')
+        self.set_place_id_prefix('P%04d')
+        self.set_event_id_prefix('E%04d')
+        self.set_repository_id_prefix('R%04d')
+        self.set_note_id_prefix('N%04d')
+        # ----------------------------------
+        self.id_trans  = DjangoTxn("ID Transaction", self, self.dji.Person)
+        self.fid_trans = DjangoTxn("FID Transaction", self, self.dji.Family)
+        self.pid_trans = DjangoTxn("PID Transaction", self, self.dji.Place)
+        self.cid_trans = DjangoTxn("CID Transaction", self, self.dji.Citation)
+        self.sid_trans = DjangoTxn("SID Transaction", self, self.dji.Source)
+        self.oid_trans = DjangoTxn("OID Transaction", self, self.dji.Media)
+        self.rid_trans = DjangoTxn("RID Transaction", self, self.dji.Repository)
+        self.nid_trans = DjangoTxn("NID Transaction", self, self.dji.Note)
+        self.eid_trans = DjangoTxn("EID Transaction", self, self.dji.Event)
+        self.cmap_index = 0
+        self.smap_index = 0
+        self.emap_index = 0
+        self.pmap_index = 0
+        self.fmap_index = 0
+        self.lmap_index = 0
+        self.omap_index = 0
+        self.rmap_index = 0
+        self.nmap_index = 0
+        self.env = Environment(self)
+        self.person_map = Map(Table(self._tables["Person"]))
+        self.family_map = Map(Table(self._tables["Family"]))
+        self.place_map  = Map(Table(self._tables["Place"]))
+        self.citation_map = Map(Table(self._tables["Citation"]))
+        self.source_map = Map(Table(self._tables["Source"]))
+        self.repository_map  = Map(Table(self._tables["Repository"]))
+        self.note_map = Map(Table(self._tables["Note"]))
+        self.media_map  = Map(Table(self._tables["Media"]))
+        self.event_map  = Map(Table(self._tables["Event"]))
+        self.tag_map  = Map(Table(self._tables["Tag"]))
+        self.metadata   = Map(Table({"cursor_func": lambda: MetaCursor()}))
+        self.name_group = {}
+        self.event_names = set()
+        self.individual_attributes = set()
+        self.family_attributes = set()
+        self.source_attributes = set()
+        self.child_ref_types = set()
+        self.family_rel_types = set()
+        self.event_role_names = set()
+        self.name_types = set()
+        self.origin_types = set()
+        self.repository_types = set()
+        self.note_types = set()
+        self.source_media_types = set()
+        self.url_types = set()
+        self.media_attributes = set()
+        self.place_types = set()
+
+    def prepare_import(self):
+        """
+        DbDjango does not commit data on gedcom import, but saves them
+        for later commit.
+        """
+        self.use_import_cache = True
+        self.import_cache = {}
+
+    @transaction.atomic
+    def commit_import(self):
+        """
+        Commits the items that were queued up during the last gedcom
+        import for two step adding.
+        """
+        # First we add the primary objects:
+        for key in list(self.import_cache.keys()):
+            obj = self.import_cache[key]
+            if isinstance(obj, Person):
+                self.dji.add_person(obj.serialize())
+            elif isinstance(obj, Family):
+                self.dji.add_family(obj.serialize())
+            elif isinstance(obj, Event):
+                self.dji.add_event(obj.serialize())
+            elif isinstance(obj, Place):
+                self.dji.add_place(obj.serialize())
+            elif isinstance(obj, Repository):
+                self.dji.add_repository(obj.serialize())
+            elif isinstance(obj, Citation):
+                self.dji.add_citation(obj.serialize())
+            elif isinstance(obj, Source):
+                self.dji.add_source(obj.serialize())
+            elif isinstance(obj, Note):
+                self.dji.add_note(obj.serialize())
+            elif isinstance(obj, MediaObject):
+                self.dji.add_media(obj.serialize())
+            elif isinstance(obj, Tag):
+                self.dji.add_tag(obj.serialize())
+        # Next we add the links:
+        for key in list(self.import_cache.keys()):
+            obj = self.import_cache[key]
+            if isinstance(obj, Person):
+                self.dji.add_person_detail(obj.serialize())
+            elif isinstance(obj, Family):
+                self.dji.add_family_detail(obj.serialize())
+            elif isinstance(obj, Event):
+                self.dji.add_event_detail(obj.serialize())
+            elif isinstance(obj, Place):
+                self.dji.add_place_detail(obj.serialize())
+            elif isinstance(obj, Repository):
+                self.dji.add_repository_detail(obj.serialize())
+            elif isinstance(obj, Citation):
+                self.dji.add_citation_detail(obj.serialize())
+            elif isinstance(obj, Source):
+                self.dji.add_source_detail(obj.serialize())
+            elif isinstance(obj, Note):
+                self.dji.add_note_detail(obj.serialize())
+            elif isinstance(obj, MediaObject):
+                self.dji.add_media_detail(obj.serialize())
+            elif isinstance(obj, Tag):
+                self.dji.add_tag_detail(obj.serialize())
+        self.use_import_cache = False
+        self.import_cache = {}
+        self.dji.update_publics()
+
+    def transaction_commit(self, txn):
+        pass
+
+    def request_rebuild(self):
+        # caches are ok, but let's compute public's
+        self.dji.update_publics()
+        self.emit('person-rebuild')
+        self.emit('family-rebuild')
+        self.emit('place-rebuild')
+        self.emit('source-rebuild')
+        self.emit('citation-rebuild')
+        self.emit('media-rebuild')
+        self.emit('event-rebuild')
+        self.emit('repository-rebuild')
+        self.emit('note-rebuild')
+        self.emit('tag-rebuild')
+
+    def get_undodb(self):
+        return None
+
+    def transaction_abort(self, txn):
+        pass
+
+    @staticmethod
+    def _validated_id_prefix(val, default):
+        if isinstance(val, str) and val:
+            try:
+                str_ = val % 1
+            except TypeError:           # missing conversion specifier
+                prefix_var = val + "%d"
+            except ValueError:          # incomplete format
+                prefix_var = default+"%04d"
+            else:
+                prefix_var = val        # OK as given
+        else:
+            prefix_var = default+"%04d" # not a string or empty string
+        return prefix_var
+
+    @staticmethod
+    def __id2user_format(id_pattern):
+        """
+        Return a method that accepts a Gramps ID and adjusts it to the users
+        format.
+        """
+        pattern_match = re.match(r"(.*)%[0 ](\d+)[diu]$", id_pattern)
+        if pattern_match:
+            str_prefix = pattern_match.group(1)
+            nr_width = pattern_match.group(2)
+            def closure_func(gramps_id):
+                if gramps_id and gramps_id.startswith(str_prefix):
+                    id_number = gramps_id[len(str_prefix):]
+                    if id_number.isdigit():
+                        id_value = int(id_number, 10)
+                        #if len(str(id_value)) > nr_width:
+                        #    # The ID to be imported is too large to fit in the
+                        #    # users format. For now just create a new ID,
+                        #    # because that is also what happens with IDs that
+                        #    # are identical to IDs already in the database. If
+                        #    # the problem of colliding import and already
+                        #    # present IDs is solved the code here also needs
+                        #    # some solution.
+                        #    gramps_id = id_pattern % 1
+                        #else:
+                        gramps_id = id_pattern % id_value
+                return gramps_id
+        else:
+            def closure_func(gramps_id):
+                return gramps_id
+        return closure_func
+
+    def set_person_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Person ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as I%d or I%04d.
+        """
+        self.person_prefix = self._validated_id_prefix(val, "I")
+        self.id2user_format = self.__id2user_format(self.person_prefix)
+
+    def set_citation_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Citation ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as C%d or C%04d.
+        """
+        self.citation_prefix = self._validated_id_prefix(val, "C")
+        self.cid2user_format = self.__id2user_format(self.citation_prefix)
+            
+    def set_source_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Source ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as S%d or S%04d.
+        """
+        self.source_prefix = self._validated_id_prefix(val, "S")
+        self.sid2user_format = self.__id2user_format(self.source_prefix)
+            
+    def set_object_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS MediaObject ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as O%d or O%04d.
+        """
+        self.mediaobject_prefix = self._validated_id_prefix(val, "O")
+        self.oid2user_format = self.__id2user_format(self.mediaobject_prefix)
+
+    def set_place_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Place ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as P%d or P%04d.
+        """
+        self.place_prefix = self._validated_id_prefix(val, "P")
+        self.pid2user_format = self.__id2user_format(self.place_prefix)
+
+    def set_family_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Family ID values. The string is
+        expected to be in the form of a simple text string, or in a format
+        that contains a C/Python style format string using %d, such as F%d
+        or F%04d.
+        """
+        self.family_prefix = self._validated_id_prefix(val, "F")
+        self.fid2user_format = self.__id2user_format(self.family_prefix)
+
+    def set_event_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Event ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as E%d or E%04d.
+        """
+        self.event_prefix = self._validated_id_prefix(val, "E")
+        self.eid2user_format = self.__id2user_format(self.event_prefix)
+
+    def set_repository_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Repository ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as R%d or R%04d.
+        """
+        self.repository_prefix = self._validated_id_prefix(val, "R")
+        self.rid2user_format = self.__id2user_format(self.repository_prefix)
+
+    def set_note_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Note ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as N%d or N%04d.
+        """
+        self.note_prefix = self._validated_id_prefix(val, "N")
+        self.nid2user_format = self.__id2user_format(self.note_prefix)
+
+    def __find_next_gramps_id(self, prefix, map_index, trans):
+        """
+        Helper function for find_next_<object>_gramps_id methods
+        """
+        index = prefix % map_index
+        while trans.get(str(index), txn=self.txn) is not None:
+            map_index += 1
+            index = prefix % map_index
+        map_index += 1
+        return (map_index, index)
+        
+    def find_next_person_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Person object based off the 
+        person ID prefix.
+        """
+        self.pmap_index, gid = self.__find_next_gramps_id(self.person_prefix,
+                                          self.pmap_index, self.id_trans)
+        return gid
+
+    def find_next_place_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Place object based off the 
+        place ID prefix.
+        """
+        self.lmap_index, gid = self.__find_next_gramps_id(self.place_prefix,
+                                          self.lmap_index, self.pid_trans)
+        return gid
+
+    def find_next_event_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Event object based off the 
+        event ID prefix.
+        """
+        self.emap_index, gid = self.__find_next_gramps_id(self.event_prefix,
+                                          self.emap_index, self.eid_trans)
+        return gid
+
+    def find_next_object_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a MediaObject object based
+        off the media object ID prefix.
+        """
+        self.omap_index, gid = self.__find_next_gramps_id(self.mediaobject_prefix,
+                                          self.omap_index, self.oid_trans)
+        return gid
+
+    def find_next_citation_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Citation object based off the 
+        citation ID prefix.
+        """
+        self.cmap_index, gid = self.__find_next_gramps_id(self.citation_prefix,
+                                          self.cmap_index, self.cid_trans)
+        return gid
+
+    def find_next_source_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Source object based off the 
+        source ID prefix.
+        """
+        self.smap_index, gid = self.__find_next_gramps_id(self.source_prefix,
+                                          self.smap_index, self.sid_trans)
+        return gid
+
+    def find_next_family_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Family object based off the 
+        family ID prefix.
+        """
+        self.fmap_index, gid = self.__find_next_gramps_id(self.family_prefix,
+                                          self.fmap_index, self.fid_trans)
+        return gid
+
+    def find_next_repository_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Respository object based 
+        off the repository ID prefix.
+        """
+        self.rmap_index, gid = self.__find_next_gramps_id(self.repository_prefix,
+                                          self.rmap_index, self.rid_trans)
+        return gid
+
+    def find_next_note_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Note object based off the 
+        note ID prefix.
+        """
+        self.nmap_index, gid = self.__find_next_gramps_id(self.note_prefix,
+                                          self.nmap_index, self.nid_trans)
+        return gid
+
+    def get_mediapath(self):
+        return None
+
+    def get_name_group_keys(self):
+        return []
+
+    def get_name_group_mapping(self, key):
+        return None
+
+    def get_researcher(self):
+        obj = Researcher()
+        return obj
+
+    def get_tag_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Tag.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Tag.all()]
+
+    def get_person_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Person.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Person.all()]
+
+    def get_family_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Family.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Family.all()]
+
+    def get_event_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Event.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Event.all()]
+
+    def get_citation_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Citation.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Citation.all()]
+
+    def get_source_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Source.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Source.all()]
+
+    def get_place_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Place.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Place.all()]
+
+    def get_repository_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Repository.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Repository.all()]
+
+    def get_media_object_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Media.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Media.all()]
+
+    def get_note_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Note.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Note.all()]
+
+    def get_media_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            media = self.dji.Media.get(handle=handle)
+        except:
+            return None
+        return self.make_media(media)
+
+    def get_event_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            event = self.dji.Event.get(handle=handle)
+        except:
+            return None
+        return self.make_event(event)
+
+    def get_family_from_handle(self, handle): 
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            family = self.dji.Family.get(handle=handle)
+        except:
+            return None
+        return self.make_family(family)
+
+    def get_repository_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            repository = self.dji.Repository.get(handle=handle)
+        except:
+            return None
+        return self.make_repository(repository)
+
+    def get_person_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            person = self.dji.Person.get(handle=handle)
+        except:
+            return None
+        return self.make_person(person)
+
+    def get_tag_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            tag = self.dji.Tag.get(handle=handle)
+        except:
+            return None
+        return self.make_tag(tag)
+
+    def make_repository(self, repository):
+        if self.use_db_cache and repository.cache:
+            data = repository.from_cache()
+        else:
+            data = self.dji.get_repository(repository)
+        return Repository.create(data)
+
+    def make_citation(self, citation):
+        if self.use_db_cache and citation.cache:
+            data = citation.from_cache()
+        else:
+            data = self.dji.get_citation(citation)
+        return Citation.create(data)
+
+    def make_source(self, source):
+        if self.use_db_cache and source.cache:
+            data = source.from_cache()
+        else:
+            data = self.dji.get_source(source)
+        return Source.create(data)
+
+    def make_family(self, family):
+        if self.use_db_cache and family.cache:
+            data = family.from_cache()
+        else:
+            data = self.dji.get_family(family)
+        return Family.create(data)
+
+    def make_person(self, person):
+        if self.use_db_cache and person.cache:
+            data = person.from_cache()
+        else:
+            data = self.dji.get_person(person)
+        return Person.create(data)
+
+    def make_event(self, event):
+        if self.use_db_cache and event.cache:
+            data = event.from_cache()
+        else:
+            data = self.dji.get_event(event)
+        return Event.create(data)
+
+    def make_note(self, note):
+        if self.use_db_cache and note.cache:
+            data = note.from_cache()
+        else:
+            data = self.dji.get_note(note)
+        return Note.create(data)
+
+    def make_tag(self, tag):
+        data = self.dji.get_tag(tag)
+        return Tag.create(data)
+
+    def make_place(self, place):
+        if self.use_db_cache and place.cache:
+            data = place.from_cache()
+        else:
+            data = self.dji.get_place(place)
+        return Place.create(data)
+
+    def make_media(self, media):
+        if self.use_db_cache and media.cache:
+            data = media.from_cache()
+        else:
+            data = self.dji.get_media(media)
+        return MediaObject.create(data)
+
+    def get_place_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            place = self.dji.Place.get(handle=handle)
+        except:
+            return None
+        return self.make_place(place)
+
+    def get_citation_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            citation = self.dji.Citation.get(handle=handle)
+        except:
+            return None
+        return self.make_citation(citation)
+
+    def get_source_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            source = self.dji.Source.get(handle=handle)
+        except:
+            return None
+        return self.make_source(source)
+
+    def get_note_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            note = self.dji.Note.get(handle=handle)
+        except:
+            return None
+        return self.make_note(note)
+
+    def get_object_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            media = self.dji.Media.get(handle=handle)
+        except:
+            return None
+        return self.make_media(media)
+
+    def get_default_person(self):
+        people = self.dji.Person.all()
+        if people.count() > 0:
+            return self.make_person(people[0])
+        return None
+
+    def iter_people(self):
+        return (self.get_person_from_handle(person.handle) 
+                for person in self.dji.Person.all())
+
+    def iter_person_handles(self):
+        return (person.handle for person in self.dji.Person.all())
+
+    def iter_families(self):
+        return (self.get_family_from_handle(family.handle) 
+                for family in self.dji.Family.all())
+
+    def iter_family_handles(self):
+        return (family.handle for family in self.dji.Family.all())
+
+    def iter_notes(self):
+        return (self.get_note_from_handle(note.handle) 
+                for note in self.dji.Note.all())
+
+    def iter_note_handles(self):
+        return (note.handle for note in self.dji.Note.all())
+
+    def iter_events(self):
+        return (self.get_event_from_handle(event.handle) 
+                for event in self.dji.Event.all())
+
+    def iter_event_handles(self):
+        return (event.handle for event in self.dji.Event.all())
+
+    def iter_places(self):
+        return (self.get_place_from_handle(place.handle) 
+                for place in self.dji.Place.all())
+
+    def iter_place_handles(self):
+        return (place.handle for place in self.dji.Place.all())
+
+    def iter_repositories(self):
+        return (self.get_repository_from_handle(repository.handle) 
+                for repository in self.dji.Repository.all())
+
+    def iter_repository_handles(self):
+        return (repository.handle for repository in self.dji.Repository.all())
+
+    def iter_sources(self):
+        return (self.get_source_from_handle(source.handle) 
+                for source in self.dji.Source.all())
+
+    def iter_source_handles(self):
+        return (source.handle for source in self.dji.Source.all())
+
+    def iter_citations(self):
+        return (self.get_citation_from_handle(citation.handle) 
+                for citation in self.dji.Citation.all())
+
+    def iter_citation_handles(self):
+        return (citation.handle for citation in self.dji.Citation.all())
+
+    def iter_tags(self):
+        return (self.get_tag_from_handle(tag.handle) 
+                for tag in self.dji.Tag.all())
+
+    def iter_tag_handles(self):
+        return (tag.handle for tag in self.dji.Tag.all())
+
+    def iter_media_objects(self):
+        return (self.get_media_from_handle(media.handle) 
+                for media in self.dji.Media.all())
+
+    def get_tag_from_name(self, name):
+        try:
+            tag = self.dji.Tag.filter(name=name)
+            return self.make_tag(tag[0])
+        except:
+            return None
+
+    def get_person_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Person.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_person(match_list[0])
+        else:
+            return None
+
+    def get_family_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        try:
+            family = self.dji.Family.get(gramps_id=gramps_id)
+        except:
+            return None
+        return self.make_family(family)
+
+    def get_source_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Source.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_source(match_list[0])
+        else:
+            return None
+
+    def get_citation_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Citation.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_citation(match_list[0])
+        else:
+            return None
+
+    def get_event_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Event.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_event(match_list[0])
+        else:
+            return None
+
+    def get_object_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Media.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_media(match_list[0])
+        else:
+            return None
+
+    def get_place_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Place.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_place(match_list[0])
+        else:
+            return None
+
+    def get_repository_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Repsoitory.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_repository(match_list[0])
+        else:
+            return None
+
+    def get_note_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Note.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_note(match_list[0])
+        else:
+            return None
+
+    def get_number_of_people(self):
+        return self.dji.Person.count()
+
+    def get_number_of_events(self):
+        return self.dji.Event.count()
+
+    def get_number_of_places(self):
+        return self.dji.Place.count()
+
+    def get_number_of_tags(self):
+        return self.dji.Tag.count()
+
+    def get_number_of_families(self):
+        return self.dji.Family.count()
+
+    def get_number_of_notes(self):
+        return self.dji.Note.count()
+
+    def get_number_of_citations(self):
+        return self.dji.Citation.count()
+
+    def get_number_of_sources(self):
+        return self.dji.Source.count()
+
+    def get_number_of_media_objects(self):
+        return self.dji.Media.count()
+
+    def get_number_of_repositories(self):
+        return self.dji.Repository.count()
+
+    def get_place_cursor(self):
+        return Cursor(self.dji.Place, self.get_raw_place_data)
+
+    def get_person_cursor(self):
+        return Cursor(self.dji.Person, self.get_raw_person_data)
+
+    def get_family_cursor(self):
+        return Cursor(self.dji.Family, self.get_raw_family_data)
+
+    def get_event_cursor(self):
+        return Cursor(self.dji.Event, self.get_raw_event_data)
+
+    def get_citation_cursor(self):
+        return Cursor(self.dji.Citation, self.get_raw_citation_data)
+
+    def get_source_cursor(self):
+        return Cursor(self.dji.Source, self.get_raw_source_data)
+
+    def get_note_cursor(self):
+        return Cursor(self.dji.Note, self.get_raw_note_data)
+
+    def get_tag_cursor(self):
+        return Cursor(self.dji.Tag, self.get_raw_tag_data)
+
+    def get_repository_cursor(self):
+        return Cursor(self.dji.Repository, self.get_raw_repository_data)
+
+    def get_media_cursor(self):
+        return Cursor(self.dji.Media, self.get_raw_object_data)
+
+    def has_gramps_id(self, obj_key, gramps_id):
+        key2table = {
+            PERSON_KEY:     self.dji.Person, 
+            FAMILY_KEY:     self.dji.Family, 
+            SOURCE_KEY:     self.dji.Source, 
+            CITATION_KEY:   self.dji.Citation, 
+            EVENT_KEY:      self.dji.Event, 
+            MEDIA_KEY:      self.dji.Media, 
+            PLACE_KEY:      self.dji.Place, 
+            REPOSITORY_KEY: self.dji.Repository, 
+            NOTE_KEY:       self.dji.Note, 
+            }
+        table = key2table[obj_key]
+        return table.filter(gramps_id=gramps_id).count() > 0
+
+    def has_person_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Person.filter(handle=handle).count() == 1
+
+    def has_family_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Family.filter(handle=handle).count() == 1
+
+    def has_citation_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Citation.filter(handle=handle).count() == 1
+
+    def has_source_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Source.filter(handle=handle).count() == 1
+
+    def has_repository_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Repository.filter(handle=handle).count() == 1
+
+    def has_note_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Note.filter(handle=handle).count() == 1
+
+    def has_place_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Place.filter(handle=handle).count() == 1
+
+    def has_event_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Event.filter(handle=handle).count() == 1
+
+    def has_tag_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Tag.filter(handle=handle).count() == 1
+
+    def has_object_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Media.filter(handle=handle).count() == 1
+
+    def has_name_group_key(self, key):
+        # FIXME:
+        return False
+
+    def set_name_group_mapping(self, key, value):
+        # FIXME:
+        pass
+
+    def set_default_person_handle(self, handle):
+        pass
+
+    def set_mediapath(self, mediapath):
+        pass
+
+    def get_raw_person_data(self, handle):
+        try:
+            return self.dji.get_person(self.dji.Person.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_family_data(self, handle):
+        try:
+            return self.dji.get_family(self.dji.Family.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_citation_data(self, handle):
+        try:
+            return self.dji.get_citation(self.dji.Citation.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_source_data(self, handle):
+        try:
+            return self.dji.get_source(self.dji.Source.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_repository_data(self, handle):
+        try:
+            return self.dji.get_repository(self.dji.Repository.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_note_data(self, handle):
+        try:
+            return self.dji.get_note(self.dji.Note.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_place_data(self, handle):
+        try:
+            return self.dji.get_place(self.dji.Place.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_object_data(self, handle):
+        try:
+            return self.dji.get_media(self.dji.Media.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_tag_data(self, handle):
+        try:
+            return self.dji.get_tag(self.dji.Tag.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_event_data(self, handle):
+        try:
+            return self.dji.get_event(self.dji.Event.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def add_person(self, person, trans, set_gid=True):
+        if not person.handle:
+            person.handle = create_id()
+        if not person.gramps_id and set_gid:
+            person.gramps_id = self.find_next_person_gramps_id()
+        self.commit_person(person, trans)
+        self.emit("person-add", ([person.handle],))
+        return person.handle
+
+    def add_family(self, family, trans, set_gid=True):
+        if not family.handle:
+            family.handle = create_id()
+        if not family.gramps_id and set_gid:
+            family.gramps_id = self.find_next_family_gramps_id()
+        self.commit_family(family, trans)
+        self.emit("family-add", ([family.handle],))
+        return family.handle
+
+    def add_citation(self, citation, trans, set_gid=True):
+        if not citation.handle:
+            citation.handle = create_id()
+        if not citation.gramps_id and set_gid:
+            citation.gramps_id = self.find_next_citation_gramps_id()
+        self.commit_citation(citation, trans)
+        self.emit("citation-add", ([citation.handle],))
+        return citation.handle
+
+    def add_source(self, source, trans, set_gid=True):
+        if not source.handle:
+            source.handle = create_id()
+        if not source.gramps_id and set_gid:
+            source.gramps_id = self.find_next_source_gramps_id()
+        self.commit_source(source, trans)
+        self.emit("source-add", ([source.handle],))
+        return source.handle
+
+    def add_repository(self, repository, trans, set_gid=True):
+        if not repository.handle:
+            repository.handle = create_id()
+        if not repository.gramps_id and set_gid:
+            repository.gramps_id = self.find_next_repository_gramps_id()
+        self.commit_repository(repository, trans)
+        self.emit("repository-add", ([repository.handle],))
+        return repository.handle
+
+    def add_note(self, note, trans, set_gid=True):
+        if not note.handle:
+            note.handle = create_id()
+        if not note.gramps_id and set_gid:
+            note.gramps_id = self.find_next_note_gramps_id()
+        self.commit_note(note, trans)
+        self.emit("note-add", ([note.handle],))
+        return note.handle
+
+    def add_place(self, place, trans, set_gid=True):
+        if not place.handle:
+            place.handle = create_id()
+        if not place.gramps_id and set_gid:
+            place.gramps_id = self.find_next_place_gramps_id()
+        self.commit_place(place, trans)
+        return place.handle
+
+    def add_event(self, event, trans, set_gid=True):
+        if not event.handle:
+            event.handle = create_id()
+        if not event.gramps_id and set_gid:
+            event.gramps_id = self.find_next_event_gramps_id()
+        self.commit_event(event, trans)
+        return event.handle
+
+    def add_tag(self, tag, trans):
+        if not tag.handle:
+            tag.handle = create_id()
+        self.commit_event(tag, trans)
+        return tag.handle
+
+    def add_object(self, obj, transaction, set_gid=True):
+        """
+        Add a MediaObject to the database, assigning internal IDs if they have
+        not already been defined.
+        
+        If not set_gid, then gramps_id is not set.
+        """
+        if not obj.handle:
+            obj.handle = create_id()
+        if not obj.gramps_id and set_gid:
+            obj.gramps_id = self.find_next_object_gramps_id()
+        self.commit_media_object(obj, transaction)
+        return obj.handle
+
+    def commit_person(self, person, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[person.handle] = person
+        else:
+            raw = person.serialize()
+            items = self.dji.Person.filter(handle=person.handle)
+            if items.count() > 0:
+                # Hack, for the moment: delete and re-add
+                items[0].delete()
+            self.dji.add_person(person.serialize())
+            self.dji.add_person_detail(person.serialize())
+            if items.count() > 0:
+                self.emit("person-update", ([person.handle],))
+            else:
+                self.emit("person-add", ([person.handle],))
+
+    def commit_family(self, family, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[family.handle] = family
+        else:
+            raw = family.serialize()
+            items = self.dji.Family.filter(handle=family.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_family(family.serialize())
+            self.dji.add_family_detail(family.serialize())
+            if items.count() > 0:
+                self.emit("family-update", ([family.handle],))
+            else:
+                self.emit("family-add", ([family.handle],))
+
+    def commit_citation(self, citation, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[citation.handle] = citation
+        else:
+            raw = citation.serialize()
+            items = self.dji.Citation.filter(handle=citation.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_citation(citation.serialize())
+            self.dji.add_citation_detail(citation.serialize())
+            if items.count() > 0:
+                self.emit("citation-update", ([citation.handle],))
+            else:
+                self.emit("citation-add", ([citation.handle],))
+
+    def commit_source(self, source, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[source.handle] = source
+        else:
+            raw = source.serialize()
+            items = self.dji.Source.filter(handle=source.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_source(source.serialize())
+            self.dji.add_source_detail(source.serialize())
+            if items.count() > 0:
+                self.emit("source-update", ([source.handle],))
+            else:
+                self.emit("source-add", ([source.handle],))
+
+    def commit_repository(self, repository, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[repository.handle] = repository
+        else:
+            raw = repository.serialize()
+            items = self.dji.Repository.filter(handle=repository.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_repository(repository.serialize())
+            self.dji.add_repository_detail(repository.serialize())
+            if items.count() > 0:
+                self.emit("repository-update", ([repository.handle],))
+            else:
+                self.emit("repository-add", ([repository.handle],))
+
+    def commit_note(self, note, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[note.handle] = note
+        else:
+            raw = note.serialize()
+            items = self.dji.Note.filter(handle=note.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_note(note.serialize())
+            self.dji.add_note_detail(note.serialize())
+            if items.count() > 0:
+                self.emit("note-update", ([note.handle],))
+            else:
+                self.emit("note-add", ([note.handle],))
+
+    def commit_place(self, place, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[place.handle] = place
+        else:
+            raw = place.serialize()
+            items = self.dji.Place.filter(handle=place.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_place(place.serialize())
+            self.dji.add_place_detail(place.serialize())
+            if items.count() > 0:
+                self.emit("place-update", ([place.handle],))
+            else:
+                self.emit("place-add", ([place.handle],))
+
+    def commit_event(self, event, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[event.handle] = event
+        else:
+            raw = event.serialize()
+            items = self.dji.Event.filter(handle=event.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_event(event.serialize())
+            self.dji.add_event_detail(event.serialize())
+            if items.count() > 0:
+                self.emit("event-update", ([event.handle],))
+            else:
+                self.emit("event-add", ([event.handle],))
+
+    def commit_tag(self, tag, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[tag.handle] = tag
+        else:
+            raw = tag.serialize()
+            items = self.dji.Tag.filter(handle=tag.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_tag(tag.serialize())
+            self.dji.add_tag_detail(tag.serialize())
+            if items.count() > 0:
+                self.emit("tag-update", ([tag.handle],))
+            else:
+                self.emit("tag-add", ([tag.handle],))
+
+    def commit_media_object(self, media, transaction, change_time=None):
+        """
+        Commit the specified MediaObject to the database, storing the changes
+        as part of the transaction.
+        """
+        if self.use_import_cache:
+            self.import_cache[obj.handle] = media
+        else:
+            raw = media.serialize()
+            items = self.dji.Media.filter(handle=media.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_media(media.serialize())
+            self.dji.add_media_detail(media.serialize())
+            if items.count() > 0:
+                self.emit("media-update", ([media.handle],))
+            else:
+                self.emit("media-add", ([media.handle],))
+
+    def get_gramps_ids(self, obj_key):
+        key2table = {
+            PERSON_KEY:     self.id_trans, 
+            FAMILY_KEY:     self.fid_trans, 
+            CITATION_KEY:   self.cid_trans, 
+            SOURCE_KEY:     self.sid_trans, 
+            EVENT_KEY:      self.eid_trans, 
+            MEDIA_KEY:      self.oid_trans, 
+            PLACE_KEY:      self.pid_trans, 
+            REPOSITORY_KEY: self.rid_trans, 
+            NOTE_KEY:       self.nid_trans, 
+            }
+
+        table = key2table[obj_key]
+        return list(table.keys())
+
+    def transaction_begin(self, transaction):
+        return 
+
+    def set_researcher(self, owner):
+        pass
+
+    def copy_from_db(self, db):
+        """
+        A (possibily) implementation-specific method to get data from
+        db into this database.
+        """
+        # First we add the primary objects:
+        for key in db._tables.keys():
+            cursor = db._tables[key]["cursor_func"]
+            for (handle, data) in cursor():
+                if key == "Person":
+                    self.dji.add_person(data)
+                elif key == "Family":
+                    self.dji.add_family(data)
+                elif key == "Event":
+                    self.dji.add_event(data)
+                elif key == "Place":
+                    self.dji.add_place(data)
+                elif key == "Repository":
+                    self.dji.add_repository(data)
+                elif key == "Citation":
+                    self.dji.add_citation(data)
+                elif key == "Source":
+                    self.dji.add_source(data)
+                elif key == "Note":
+                    self.dji.add_note(data)
+                elif key == "Media":
+                    self.dji.add_media(data)
+                elif key == "Tag":
+                    self.dji.add_tag(data)
+        for key in db._tables.keys():
+            cursor = db._tables[key]["cursor_func"]
+            for (handle, data) in cursor():
+                if key == "Person":
+                    self.dji.add_person_detail(data)
+                elif key == "Family":
+                    self.dji.add_family_detail(data)
+                elif key == "Event":
+                    self.dji.add_event_detail(data)
+                elif key == "Place":
+                    self.dji.add_place_detail(data)
+                elif key == "Repository":
+                    self.dji.add_repository_detail(data)
+                elif key == "Citation":
+                    self.dji.add_citation_detail(data)
+                elif key == "Source":
+                    self.dji.add_source_detail(data)
+                elif key == "Note":
+                    self.dji.add_note_detail(data)
+                elif key == "Media":
+                    self.dji.add_media_detail(data)
+                elif key == "Tag":
+                    self.dji.add_tag_detail(data)
+            # Next we add the links:
+        self.dji.update_publics()
+
+    def get_from_name_and_handle(self, table_name, handle):
+        """
+        Returns a gen.lib object (or None) given table_name and
+        handle.
+
+        Examples:
+
+        >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008")
+        >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23")
+        """
+        if table_name in self._tables:
+            return self._tables[table_name]["handle_func"](handle)
+        return None
+
+    def is_empty(self):
+        """
+        Is the database empty?
+        """
+        return (self.get_number_of_people() == 0 and
+                self.get_number_of_events() == 0 and
+                self.get_number_of_places() == 0 and
+                self.get_number_of_tags() == 0 and
+                self.get_number_of_families() == 0 and
+                self.get_number_of_notes() == 0 and
+                self.get_number_of_citations() == 0 and
+                self.get_number_of_sources() == 0 and
+                self.get_number_of_media_objects() == 0 and
+                self.get_number_of_repositories() == 0)
+                
+    __callback_map = {}
+
+    def set_prefixes(self, person, media, family, source, citation,
+                     place, event, repository, note):
+        self.set_person_id_prefix(person)
+        self.set_object_id_prefix(media)
+        self.set_family_id_prefix(family)
+        self.set_source_id_prefix(source)
+        self.set_citation_id_prefix(citation)
+        self.set_place_id_prefix(place)
+        self.set_event_id_prefix(event)
+        self.set_repository_id_prefix(repository)
+        self.set_note_id_prefix(note)
+
+    def has_changed(self):
+        return False
+
+    def find_backlink_handles(self, handle, include_classes=None):
+        ## FIXME: figure out how to get objects that refer
+        ## to this handle
+        return []
+
+    def get_note_bookmarks(self):
+        return self.note_bookmarks
+
+    def get_media_bookmarks(self):
+        return self.media_bookmarks
+
+    def get_repo_bookmarks(self):
+        return self.repo_bookmarks
+
+    def get_citation_bookmarks(self):
+        return self.citation_bookmarks
+
+    def get_source_bookmarks(self):
+        return self.source_bookmarks
+
+    def get_place_bookmarks(self):
+        return self.place_bookmarks
+
+    def get_event_bookmarks(self):
+        return self.event_bookmarks
+
+    def get_bookmarks(self):
+        return self.bookmarks
+
+    def get_family_bookmarks(self):
+        return self.family_bookmarks
+
+    def get_save_path(self):
+        return "/tmp/"
+
+    ## Get types:
+    def get_event_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Event instances
+        in the database.
+        """
+        return list(self.event_attributes)
+
+    def get_event_types(self):
+        """
+        Return a list of all event types in the database.
+        """
+        return list(self.event_names)
+
+    def get_person_event_types(self):
+        """
+        Deprecated:  Use get_event_types
+        """
+        return list(self.event_names)
+
+    def get_person_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Person instances 
+        in the database.
+        """
+        return list(self.individual_attributes)
+
+    def get_family_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Family instances 
+        in the database.
+        """
+        return list(self.family_attributes)
+
+    def get_family_event_types(self):
+        """
+        Deprecated:  Use get_event_types
+        """
+        return list(self.event_names)
+
+    def get_media_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Media and MediaRef 
+        instances in the database.
+        """
+        return list(self.media_attributes)
+
+    def get_family_relation_types(self):
+        """
+        Return a list of all relationship types assocated with Family
+        instances in the database.
+        """
+        return list(self.family_rel_types)
+
+    def get_child_reference_types(self):
+        """
+        Return a list of all child reference types assocated with Family
+        instances in the database.
+        """
+        return list(self.child_ref_types)
+
+    def get_event_roles(self):
+        """
+        Return a list of all custom event role names assocated with Event
+        instances in the database.
+        """
+        return list(self.event_role_names)
+
+    def get_name_types(self):
+        """
+        Return a list of all custom names types assocated with Person
+        instances in the database.
+        """
+        return list(self.name_types)
+
+    def get_origin_types(self):
+        """
+        Return a list of all custom origin types assocated with Person/Surname
+        instances in the database.
+        """
+        return list(self.origin_types)
+
+    def get_repository_types(self):
+        """
+        Return a list of all custom repository types assocated with Repository 
+        instances in the database.
+        """
+        return list(self.repository_types)
+
+    def get_note_types(self):
+        """
+        Return a list of all custom note types assocated with Note instances 
+        in the database.
+        """
+        return list(self.note_types)
+
+    def get_source_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Source/Citation
+        instances in the database.
+        """
+        return list(self.source_attributes)
+
+    def get_source_media_types(self):
+        """
+        Return a list of all custom source media types assocated with Source 
+        instances in the database.
+        """
+        return list(self.source_media_types)
+
+    def get_url_types(self):
+        """
+        Return a list of all custom names types assocated with Url instances 
+        in the database.
+        """
+        return list(self.url_types)
+
+    def get_place_types(self):
+        """
+        Return a list of all custom place types assocated with Place instances
+        in the database.
+        """
+        return list(self.place_types)
+
+    def get_default_handle(self):
+        people = self.dji.Person.all()
+        if people.count() > 0:
+            return people[0].handle
+        return None
+
+    def close(self):
+        pass
+
+    def get_surname_list(self):
+        return []
+
+    def is_open(self):
+        return True
+
+    def get_table_names(self):
+        """Return a list of valid table names."""
+        return list(self._tables.keys())
+
+    def find_initial_person(self):
+        return self.get_default_person()
+
+    # Removals:
+    def remove_person(self, handle, txn):
+        self.dji.Person.filter(handle=handle)[0].delete()
+        self.emit("person-delete", ([handle],))
+
+    def remove_source(self, handle, transaction):
+        self.dji.Source.filter(handle=handle)[0].delete()
+        self.emit("source-delete", ([handle],))
+
+    def remove_citation(self, handle, transaction):
+        self.dji.Citation.filter(handle=handle)[0].delete()
+        self.emit("citation-delete", ([handle],))
+
+    def remove_event(self, handle, transaction):
+        self.dji.Event.filter(handle=handle)[0].delete()
+        self.emit("event-delete", ([handle],))
+
+    def remove_object(self, handle, transaction):
+        self.dji.Media.filter(handle=handle)[0].delete()
+        self.emit("media-delete", ([handle],))
+
+    def remove_place(self, handle, transaction):
+        self.dji.Place.filter(handle=handle)[0].delete()
+        self.emit("place-delete", ([handle],))
+
+    def remove_family(self, handle, transaction):
+        self.dji.Family.filter(handle=handle)[0].delete()
+        self.emit("family-delete", ([handle],))
+
+    def remove_repository(self, handle, transaction):
+        self.dji.Repository.filter(handle=handle)[0].delete()
+        self.emit("repository-delete", ([handle],))
+
+    def remove_note(self, handle, transaction):
+        self.dji.Note.filter(handle=handle)[0].delete()
+        self.emit("note-delete", ([handle],))
+
+    def remove_tag(self, handle, transaction):
+        self.dji.Tag.filter(handle=handle)[0].delete()
+        self.emit("tag-delete", ([handle],))
+
+    def remove_from_surname_list(self, person):
+        ## FIXME
+        pass
+    
+    def get_dbname(self):
+        return "Django Database"
+
+    ## missing
+
+    def find_place_child_handles(self, handle):
+        pass
+
+    def get_cursor(self, table, txn=None, update=False, commit=False):
+        pass
+
+    def get_from_name_and_handle(self, table_name, handle):
+        """
+        Returns a gen.lib object (or None) given table_name and
+        handle.
+
+        Examples:
+
+        >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008")
+        >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23")
+        """
+        if table_name in self._tables:
+            return self._tables[table_name]["handle_func"](handle)
+        return None
+
+    def get_number_of_records(self, table):
+        pass
+
+    def get_place_parent_cursor(self):
+        pass
+
+    def get_place_tree_cursor(self):
+        pass
+
+    def get_table_metadata(self, table_name):
+        """Return the metadata for a valid table name."""
+        if table_name in self._tables:
+            return self._tables[table_name]
+        return None
+
+    def get_transaction_class(self):
+        pass
+
+    def undo(self, update_history=True):
+        # FIXME:
+        return self.undodb.undo(update_history)
+
+    def redo(self, update_history=True):
+        # FIXME:
+        return self.undodb.redo(update_history)
+
+    def backup(self):
+        pass
+
+    def restore(self):
+        pass
diff --git a/gramps/plugins/database/dictionarydb.gpr.py b/gramps/plugins/database/dictionarydb.gpr.py
new file mode 100644
index 000000000..3b0e62eca
--- /dev/null
+++ b/gramps/plugins/database/dictionarydb.gpr.py
@@ -0,0 +1,31 @@
+#
+# Gramps - a GTK+/GNOME based genealogy program
+#
+# Copyright (C) 2015 Douglas Blank <doug.blank@gmail.com>
+#
+# 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+plg = newplugin()
+plg.id    = 'dictionarydb'
+plg.name  = _("Dictionary Database Backend")
+plg.name_accell  = _("Di_ctionary Database Backend")
+plg.description =  _("Dictionary (in-memory) Database Backend")
+plg.version = '1.0'
+plg.gramps_target_version = "4.2"
+plg.status = STABLE
+plg.fname = 'dictionarydb.py'
+plg.ptype = DATABASE
+plg.databaseclass = 'DictionaryDb'
diff --git a/gramps/plugins/database/dictionary.py b/gramps/plugins/database/dictionarydb.py
similarity index 98%
rename from gramps/plugins/database/dictionary.py
rename to gramps/plugins/database/dictionarydb.py
index 2d785ef6e..a3dbd9586 100644
--- a/gramps/plugins/database/dictionary.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -28,17 +28,17 @@ import pickle
 import base64
 import time
 import re
-from . import DbReadBase, DbWriteBase, DbTxn
-from . import (PERSON_KEY,
-               FAMILY_KEY,
-               CITATION_KEY,
-               SOURCE_KEY,
-               EVENT_KEY,
-               MEDIA_KEY,
-               PLACE_KEY,
-               REPOSITORY_KEY,
-               NOTE_KEY,
-               TAG_KEY)
+from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn
+from gramps.gen.db import (PERSON_KEY,
+                           FAMILY_KEY,
+                           CITATION_KEY,
+                           SOURCE_KEY,
+                           EVENT_KEY,
+                           MEDIA_KEY,
+                           PLACE_KEY,
+                           REPOSITORY_KEY,
+                           NOTE_KEY,
+                           TAG_KEY)
 
 from gramps.gen.utils.id import create_id
 from gramps.gen.lib.researcher import Researcher
diff --git a/gramps/plugins/database/django_support/libdjango.py b/gramps/plugins/database/django_support/libdjango.py
new file mode 100644
index 000000000..fa0a596c8
--- /dev/null
+++ b/gramps/plugins/database/django_support/libdjango.py
@@ -0,0 +1,2063 @@
+# Gramps - a GTK+/GNOME based genealogy program
+#
+# Copyright (C) 2009         Douglas S. Blank <doug.blank@gmail.com>
+#
+# 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+""" Interface to Django models """
+
+#------------------------------------------------------------------------
+#
+# Python Modules
+#
+#------------------------------------------------------------------------
+import time
+import sys
+import pickle
+import base64
+import collections
+
+#------------------------------------------------------------------------
+#
+# Django Modules
+#
+#------------------------------------------------------------------------
+from django.contrib.contenttypes.models import ContentType
+from django.db import transaction
+
+#------------------------------------------------------------------------
+#
+# Gramps Modules
+#
+#------------------------------------------------------------------------
+import gramps.webapp.grampsdb.models as models
+from gramps.gen.lib import Name
+from gramps.gen.utils.id import create_id
+from gramps.gen.constfunc import conv_to_unicode
+
+# To get a django person from a django database:
+#    djperson = dji.Person.get(handle='djhgsdh324hjg234hj24')
+# 
+# To turn the djperson into a Gramps Person:
+#    tuple = dji.get_person(djperson)
+#    gperson = lib.gen.Person(tuple)
+# OR
+#    gperson = dbdjango.DbDjango().get_person_from_handle(handle)
+
+def check_diff(item, raw):
+    encoded = str(base64.encodebytes(pickle.dumps(raw)), "utf-8")
+    if item.cache != encoded:
+        print("Different:", item.__class__.__name__, item.gramps_id)
+        print("raw  :", raw)
+        print("cache:", item.from_cache())
+        # FIXING, TOO:
+        item.save_cache()
+
+#-------------------------------------------------------------------------
+#
+# Import functions
+#
+#-------------------------------------------------------------------------
+def lookup_role_index(role0, event_ref_list):
+    """
+    Find the handle in a unserialized event_ref_list and return code.
+    """
+    if role0 is None:
+        return -1
+    else:
+        count = 0
+        for event_ref in event_ref_list:
+            (private, note_list, attribute_list, ref, erole) = event_ref
+            try:
+                event = models.Event.objects.get(handle=ref)
+            except:
+                return -1
+            if event.event_type[0] == role0:
+                return count
+            count += 1
+        return -1
+
+def totime(dtime):
+    if dtime:
+        return int(time.mktime(dtime.timetuple()))
+    else:
+        return 0
+
+#-------------------------------------------------------------------------
+#
+# Export functions
+#
+#-------------------------------------------------------------------------
+def todate(t):
+    return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(t))
+
+def lookup(index, event_ref_list):
+    """
+    Get the unserialized event_ref in an list of them and return it.
+    """
+    if index < 0:
+        return None
+    else:
+        count = 0
+        for event_ref in event_ref_list:
+            (private, note_list, attribute_list, ref, role) = event_ref
+            if index == count:
+                return ref
+            count += 1
+        return None
+
+def get_datamap(grampsclass):
+    return [x[0] for x in grampsclass._DATAMAP if x[0] != grampsclass.CUSTOM]
+
+#-------------------------------------------------------------------------
+#
+# Django Interface
+#
+#-------------------------------------------------------------------------
+class DjangoInterface(object):
+    """
+    DjangoInterface for interoperating between Gramps and Django.
+    
+    This interface comes in a number of parts: 
+        get_ITEMS()
+        add_ITEMS()
+
+    get_ITEM(ITEM)
+
+    Given an ITEM from a Django table, construct a Gramps Raw Data tuple.
+
+    add_ITEM(data)
+
+    Given a Gramps Raw Data tuple, add the data to the Django tables.
+    
+
+    """
+    def __init__(self):
+        self.debug = 0
+
+    def __getattr__(self, name):
+        """
+        Django Objects database interface.
+
+        >>> self.Person.all()
+        >>> self.Person.get(id=1)
+        >>> self.Person.get(handle='gh71234dhf3746347734')
+        """
+        if hasattr(models, name):
+            return getattr(models, name).objects
+        else:
+            raise AttributeError("no such model: '%s'" % name)
+
+    def get_next_id(self, obj, prefix):
+        """
+        Get next gramps_id
+
+        >>> dji.get_next_id(Person, "P")
+        'P0002'
+        >>> dji.get_next_id(Media, "M")
+        'M2349'
+        """
+        ids = [o["gramps_id"] for o in obj.objects.values("gramps_id")]
+        count = 1
+        while "%s%04d" % (prefix, count) in ids: 
+            count += 1
+        return "%s%04d" % (prefix, count)
+
+    def get_model(self, name):
+        if hasattr(models, name):
+            return getattr(models, name)
+        elif hasattr(models, name.title()):
+            return getattr(models, name.title())
+        else:
+            raise AttributeError("no such model: '%s'" % name)
+
+    # -----------------------------------------------
+    # Get methods to retrieve list data from the tables
+    # -----------------------------------------------
+
+    def clear_tables(self, *args):
+        return models.clear_tables(*args)
+
+    def get_tag_list(self, obj):
+        return obj.get_tag_list()
+
+    def get_attribute_list(self, obj):
+        obj_type = ContentType.objects.get_for_model(obj)
+        attribute_list = models.Attribute.objects.filter(object_id=obj.id, 
+                                                     object_type=obj_type)
+        return list(map(self.pack_attribute, attribute_list))
+
+    def get_primary_name(self, person):
+        names = person.name_set.filter(preferred=True).order_by("order")
+        if len(names) > 0:
+            return Name.create(self.pack_name(names[0]))
+        else:
+            return Name()
+      
+    def get_alternate_names(self, person):
+        names = person.name_set.filter(preferred=False).order_by("order")
+        return [Name.create(self.pack_name(n)) for n in names]
+
+    def get_names(self, person, preferred):
+        names = person.name_set.filter(preferred=preferred).order_by("order")
+        if preferred:
+            if len(names) > 0:
+                return self.pack_name(names[0])
+            else:
+                return Name().serialize()
+        else:
+            return list(map(self.pack_name, names))
+     
+    def get_source_attribute_list(self, source): 
+        return [(map.private, map.key, map.value) for map in source.sourceattribute_set.all().order_by("order")]
+
+    def get_citation_attribute_list(self, citation): 
+        return [(map.private, map.key, map.value) for map in citation.citationattribute_set.all().order_by("order")]
+
+    def get_media_list(self, obj):
+        obj_type = ContentType.objects.get_for_model(obj)
+        mediarefs = models.MediaRef.objects.filter(object_id=obj.id, 
+                                               object_type=obj_type)
+        return list(map(self.pack_media_ref, mediarefs))
+
+    def get_note_list(self, obj):
+        obj_type = ContentType.objects.get_for_model(obj)
+        noterefs = models.NoteRef.objects.filter(object_id=obj.id, 
+                                             object_type=obj_type)
+        return [noteref.ref_object.handle for noteref in noterefs]
+
+    def get_repository_ref_list(self, obj):
+        obj_type = ContentType.objects.get_for_model(obj)
+        reporefs = models.RepositoryRef.objects.filter(object_id=obj.id, 
+                                                   object_type=obj_type)
+        return list(map(self.pack_repository_ref, reporefs))
+
+    def get_place_ref_list(self, obj):
+        obj_type = ContentType.objects.get_for_model(obj)
+        refs = models.PlaceRef.objects.filter(object_id=obj.id, 
+                                              object_type=obj_type)
+        return list(map(self.pack_place_ref, refs))
+
+    def get_url_list(self, obj):
+        return list(map(self.pack_url, obj.url_set.all().order_by("order")))
+
+    def get_address_list(self, obj, with_parish): # person or repository
+        addresses = obj.address_set.all().order_by("order")
+        return [self.pack_address(address, with_parish)
+                    for address in addresses]
+
+    def get_child_ref_list(self, family):
+        obj_type = ContentType.objects.get_for_model(family)
+        childrefs = models.ChildRef.objects.filter(object_id=family.id,
+                                        object_type=obj_type).order_by("order")
+        return list(map(self.pack_child_ref, childrefs))
+
+    def get_citation_list(self, obj):
+        obj_type = ContentType.objects.get_for_model(obj)
+        citationrefs = models.CitationRef.objects.filter(object_id=obj.id,
+                                                         object_type=obj_type).order_by("order")
+        return [citationref.citation.handle for citationref in citationrefs]
+
+    def get_event_refs(self, obj, order="order"):
+        obj_type = ContentType.objects.get_for_model(obj)
+        eventrefs = models.EventRef.objects.filter(object_id=obj.id,
+                                        object_type=obj_type).order_by(order)
+        return eventrefs
+
+    def get_event_ref_list(self, obj):
+        obj_type = ContentType.objects.get_for_model(obj)
+        eventrefs = models.EventRef.objects.filter(object_id=obj.id,
+                                        object_type=obj_type).order_by("order")
+        return list(map(self.pack_event_ref, eventrefs))
+
+    def get_family_list(self, person): # person has families
+        return [fam.family.handle for fam in 
+                models.MyFamilies.objects.filter(person=person).order_by("order")]
+    
+    def get_parent_family_list(self, person): # person's parents has families
+        return [fam.family.handle for fam in 
+                models.MyParentFamilies.objects.filter(person=person).order_by("order")]
+
+    def get_person_ref_list(self, person):
+        obj_type = ContentType.objects.get_for_model(person)
+        return list(map(self.pack_person_ref, 
+                models.PersonRef.objects.filter(object_id=person.id, 
+                                            object_type=obj_type)))
+
+    def get_lds_list(self, obj): # person or family
+        return list(map(self.pack_lds, obj.lds_set.all().order_by("order")))
+
+    def get_place_handle(self, obj): # obj is event
+        if obj.place:
+            return obj.place.handle
+        return ''
+
+    ## Packers:
+
+    def get_event(self, event):
+        handle = event.handle
+        gid = event.gramps_id
+        the_type = tuple(event.event_type)
+        description = event.description
+        change = totime(event.last_changed)
+        private = event.private
+        note_list = self.get_note_list(event)           
+        citation_list = self.get_citation_list(event)   
+        media_list = self.get_media_list(event)         
+        attribute_list = self.get_attribute_list(event)
+        date = self.get_date(event)
+        place_handle = self.get_place_handle(event)
+        tag_list = self.get_tag_list(event)
+        return (str(handle), gid, the_type, date, description, place_handle, 
+                citation_list, note_list, media_list, attribute_list,
+                change, tag_list, private)
+
+    def get_note_markup(self, note):
+        retval = []
+        markups = models.Markup.objects.filter(note=note).order_by("order")
+        for markup in markups:
+            if markup.string and markup.string.isdigit():
+                value = int(markup.string)
+            else:
+                value = markup.string 
+            start_stop_list  = markup.start_stop_list
+            ss_list = eval(start_stop_list)
+            retval += [(tuple(markup.styled_text_tag_type), value, ss_list)]
+        return retval
+
+    def get_tag(self, tag):
+        changed = totime(tag.last_changed)
+        return (str(tag.handle),
+                tag.name,
+                tag.color,
+                tag.priority,
+                changed)
+
+    def get_note(self, note):
+        styled_text = [note.text, self.get_note_markup(note)]
+        changed = totime(note.last_changed)
+        tag_list = self.get_tag_list(note)
+        return (str(note.handle), 
+                note.gramps_id, 
+                styled_text, 
+                note.preformatted, 
+                tuple(note.note_type), 
+                changed, 
+                tag_list, 
+                note.private)
+
+    def get_family(self, family):
+        child_ref_list = self.get_child_ref_list(family)
+        event_ref_list = self.get_event_ref_list(family)
+        media_list = self.get_media_list(family)
+        attribute_list = self.get_attribute_list(family)
+        lds_seal_list = self.get_lds_list(family)
+        citation_list = self.get_citation_list(family)
+        note_list = self.get_note_list(family)
+        tag_list = self.get_tag_list(family)
+        if family.father:
+            father_handle = family.father.handle
+        else:
+            father_handle = ''
+        if family.mother:
+            mother_handle = family.mother.handle
+        else:
+            mother_handle = ''
+        return (str(family.handle), family.gramps_id, 
+                father_handle, mother_handle,
+                child_ref_list, tuple(family.family_rel_type), 
+                event_ref_list, media_list,
+                attribute_list, lds_seal_list, 
+                citation_list, note_list,
+                totime(family.last_changed), 
+                tag_list, 
+                family.private)
+
+    def get_repository(self, repository):
+        note_list = self.get_note_list(repository)
+        address_list = self.get_address_list(repository, with_parish=False)
+        url_list = self.get_url_list(repository)
+        tag_list = self.get_tag_list(repository)
+        return (str(repository.handle), 
+                repository.gramps_id, 
+                tuple(repository.repository_type),
+                repository.name, 
+                note_list,
+                address_list, 
+                url_list, 
+                totime(repository.last_changed), 
+                tag_list,
+                repository.private)
+
+    def get_citation(self, citation):
+        note_list = self.get_note_list(citation)
+        media_list = self.get_media_list(citation)
+        attribute_list = self.get_citation_attribute_list(citation)
+        tag_list = self.get_tag_list(citation)
+        date = self.get_date(citation)
+        # I guess citations can have no source
+        if citation.source:
+            handle = citation.source.handle
+        else:
+            handle = None
+        return (str(citation.handle),  
+                citation.gramps_id, 
+                date,
+                citation.page, 
+                citation.confidence, 
+                handle,
+                note_list, 
+                media_list, 
+                attribute_list, 
+                totime(citation.last_changed), 
+                tag_list,
+                citation.private) 
+
+    def get_source(self, source):
+        note_list = self.get_note_list(source)
+        media_list = self.get_media_list(source)
+        attribute_list = self.get_source_attribute_list(source)
+        reporef_list = self.get_repository_ref_list(source)
+        tag_list = self.get_tag_list(source)
+        return (str(source.handle), 
+                source.gramps_id, 
+                source.title,
+                source.author, 
+                source.pubinfo,
+                note_list,
+                media_list,
+                source.abbrev,
+                totime(source.last_changed), 
+                attribute_list,
+                reporef_list,
+                tag_list,
+                source.private)
+
+    def get_media(self, media):
+        attribute_list = self.get_attribute_list(media)
+        citation_list = self.get_citation_list(media)
+        note_list = self.get_note_list(media)
+        tag_list = self.get_tag_list(media)
+        date = self.get_date(media)
+        return (str(media.handle), 
+                media.gramps_id, 
+                conv_to_unicode(media.path, None),
+                str(media.mime), 
+                str(media.desc),
+                media.checksum,
+                attribute_list,
+                citation_list,
+                note_list,
+                totime(media.last_changed),
+                date,
+                tag_list,
+                media.private)
+
+    def get_person(self, person):
+        primary_name = self.get_names(person, True) # one
+        alternate_names = self.get_names(person, False) # list
+        event_ref_list = self.get_event_ref_list(person)
+        family_list = self.get_family_list(person)
+        parent_family_list = self.get_parent_family_list(person)
+        media_list = self.get_media_list(person)
+        address_list = self.get_address_list(person, with_parish=False)
+        attribute_list = self.get_attribute_list(person)
+        url_list = self.get_url_list(person)
+        lds_ord_list = self.get_lds_list(person)
+        pcitation_list = self.get_citation_list(person)
+        pnote_list = self.get_note_list(person)
+        person_ref_list = self.get_person_ref_list(person)
+        # This looks up the events for the first EventType given:
+        death_ref_index = person.death_ref_index
+        birth_ref_index = person.birth_ref_index
+        tag_list = self.get_tag_list(person)
+
+        return (str(person.handle),
+                person.gramps_id,  
+                tuple(person.gender_type)[0],
+                primary_name,       
+                alternate_names,    
+                death_ref_index,    
+                birth_ref_index,    
+                event_ref_list,     
+                family_list,        
+                parent_family_list, 
+                media_list,         
+                address_list,       
+                attribute_list,     
+                url_list,               
+                lds_ord_list,       
+                pcitation_list,       
+                pnote_list,         
+                totime(person.last_changed),             
+                tag_list, 
+                person.private,            
+                person_ref_list)
+
+    def get_date(self, obj):
+        if ((obj.calendar == obj.modifier == obj.quality == obj.sortval == obj.newyear == 0) and
+            obj.text == "" and (not obj.slash1) and (not obj.slash2) and 
+            (obj.day1 == obj.month1 == obj.year1 == 0) and 
+            (obj.day2 == obj.month2 == obj.year2 == 0)):
+            return None
+        elif ((not obj.slash1) and (not obj.slash2) and 
+            (obj.day2 == obj.month2 == obj.year2 == 0)):
+            dateval = (obj.day1, obj.month1, obj.year1, obj.slash1)
+        else:
+            dateval = (obj.day1, obj.month1, obj.year1, obj.slash1, 
+                       obj.day2, obj.month2, obj.year2, obj.slash2)
+        return (obj.calendar, obj.modifier, obj.quality, dateval, 
+                obj.text, obj.sortval, obj.newyear)
+
+    def get_place(self, place):
+        locations = place.location_set.all().order_by("order")
+        alt_location_list = [self.pack_location(location, True) for location in locations]
+        url_list = self.get_url_list(place)
+        media_list = self.get_media_list(place)
+        citation_list = self.get_citation_list(place)
+        note_list = self.get_note_list(place)
+        tag_list = self.get_tag_list(place)
+        place_ref_list = self.get_place_ref_list(place)
+        return (str(place.handle), 
+                place.gramps_id,
+                place.title, 
+                place.long, 
+                place.lat,
+                place_ref_list,
+                place.name,
+                [], ## FIXME: get_alt_names
+                tuple(place.place_type),
+                place.code,
+                alt_location_list,
+                url_list,
+                media_list,
+                citation_list,
+                note_list,
+                totime(place.last_changed), 
+                tag_list,
+                place.private)
+
+    # ---------------------------------
+    # Packers
+    # ---------------------------------
+
+    ## The packers build GRAMPS raw unserialized data.
+
+    ## Reference packers
+
+    def pack_child_ref(self, child_ref):
+        citation_list = self.get_citation_list(child_ref)
+        note_list = self.get_note_list(child_ref) 
+        return (child_ref.private, citation_list, note_list, child_ref.ref_object.handle, 
+                tuple(child_ref.father_rel_type), tuple(child_ref.mother_rel_type))
+
+    def pack_person_ref(self, personref):
+        citation_list = self.get_citation_list(personref)
+        note_list = self.get_note_list(personref)
+        return (personref.private, 
+                citation_list,
+                note_list,
+                personref.ref_object.handle,
+                personref.description)
+
+    def pack_media_ref(self, media_ref):
+        citation_list = self.get_citation_list(media_ref)
+        note_list = self.get_note_list(media_ref)
+        attribute_list = self.get_attribute_list(media_ref)
+        if ((media_ref.x1 == media_ref.y1 == media_ref.x2 == media_ref.y2 == -1) or
+            (media_ref.x1 == media_ref.y1 == media_ref.x2 == media_ref.y2 == 0)):
+            role = None
+        else:
+            role = (media_ref.x1, media_ref.y1, media_ref.x2, media_ref.y2)
+        return (media_ref.private, citation_list, note_list, attribute_list, 
+                media_ref.ref_object.handle, role)
+
+    def pack_repository_ref(self, repo_ref):
+        note_list = self.get_note_list(repo_ref)
+        return (note_list, 
+                repo_ref.ref_object.handle,
+                repo_ref.call_number, 
+                tuple(repo_ref.source_media_type),
+                repo_ref.private)
+
+    def pack_place_ref(self, place_ref):
+        date = self.get_date(place_ref)
+        return (place_ref.ref_object.handle, date)
+
+    def pack_media_ref(self, media_ref):
+        note_list = self.get_note_list(media_ref)
+        attribute_list = self.get_attribute_list(media_ref)
+        citation_list = self.get_citation_list(media_ref)
+        return (media_ref.private, citation_list, note_list, attribute_list, 
+                media_ref.ref_object.handle, (media_ref.x1,
+                                              media_ref.y1,
+                                              media_ref.x2,
+                                              media_ref.y2))
+    
+    def pack_event_ref(self, event_ref):
+        note_list = self.get_note_list(event_ref)
+        attribute_list = self.get_attribute_list(event_ref)
+        return (event_ref.private, note_list, attribute_list, 
+                event_ref.ref_object.handle, tuple(event_ref.role_type))
+
+    def pack_citation(self, citation):
+        handle = citation.handle
+        gid = citation.gramps_id
+        date = self.get_date(citation)
+        page = citation.page
+        confidence = citation.confidence
+        source_handle = citation.source.handle
+        note_list = self.get_note_list(citation)
+        media_list = self.get_media_list(citation)
+        attribute_list = self.get_citation_attribute_list(citation)
+        changed = totime(citation.last_changed)
+        private = citation.private
+        tag_list = self.get_tag_list(citation)
+        return (handle, gid, date, page, confidence, source_handle,
+                note_list, media_list, attribute_list, changed, tag_list, 
+                private) 
+
+    def pack_address(self, address, with_parish):
+        citation_list = self.get_citation_list(address)
+        date = self.get_date(address)
+        note_list = self.get_note_list(address)
+        locations = address.location_set.all().order_by("order")
+        if len(locations) > 0:
+            location = self.pack_location(locations[0], with_parish)
+        else:
+            if with_parish:
+                location = (("", "", "", "", "", "", ""), "")
+            else:
+                location = ("", "", "", "", "", "", "")
+        return (address.private, citation_list, note_list, date, location)
+
+    def pack_lds(self, lds):
+        citation_list = self.get_citation_list(lds)
+        note_list = self.get_note_list(lds)
+        date = self.get_date(lds)
+        if lds.famc:
+            famc = lds.famc.handle
+        else:
+            famc = None
+        place_handle = self.get_place_handle(lds)
+        return (citation_list, note_list, date, lds.lds_type[0], place_handle,
+                famc, lds.temple, lds.status[0], lds.private)
+
+    def pack_source(self, source):
+        note_list = self.get_note_list(source)
+        media_list = self.get_media_list(source)
+        reporef_list = self.get_repository_ref_list(source)
+        attribute_list = self.get_source_attribute_list(source)
+        tag_list = self.get_tag_list(source)
+        return (source.handle, source.gramps_id, source.title,
+                source.author, source.pubinfo,
+                note_list,
+                media_list,
+                source.abbrev,
+                totime(last_changed), attribute_list,
+                reporef_list,
+                tag_list,
+                source.private)
+
+    def pack_name(self, name):
+        citation_list = self.get_citation_list(name)
+        note_list = self.get_note_list(name)
+        date = self.get_date(name)
+        return (name.private, citation_list, note_list, date,
+                name.first_name, name.make_surname_list(), name.suffix,
+                name.title, tuple(name.name_type), 
+                name.group_as, name.sort_as.val, 
+                name.display_as.val, name.call, name.nick, 
+                name.famnick)
+
+    def pack_location(self, loc, with_parish):
+        if with_parish:
+            return ((loc.street, loc.locality, loc.city, loc.county, loc.state, loc.country, 
+                     loc.postal, loc.phone), loc.parish)
+        else:
+            return (loc.street, loc.locality, loc.city, loc.county, loc.state, loc.country, 
+                    loc.postal, loc.phone)
+
+    def pack_url(self, url):
+        return  (url.private, url.path, url.desc, tuple(url.url_type))
+
+    def pack_attribute(self, attribute):
+        citation_list = self.get_citation_list(attribute)
+        note_list = self.get_note_list(attribute)
+        return (attribute.private, 
+                citation_list, 
+                note_list, 
+                tuple(attribute.attribute_type), 
+                attribute.value)
+
+
+    ## Export lists:
+    
+    def add_child_ref_list(self, obj, ref_list):
+        ## Currently, only Family references children
+        for child_data in ref_list:
+            self.add_child_ref(obj, child_data)
+    
+    def add_citation_list(self, obj, citation_list):
+        for citation_handle in citation_list:
+            self.add_citation_ref(obj, citation_handle)
+    
+    def add_event_ref_list(self, obj, event_ref_list):
+        for event_ref in event_ref_list:
+            self.add_event_ref(obj, event_ref)
+    
+    def add_surname_list(self, name, surname_list):
+        order = 1
+        for data in surname_list:
+            (surname_text, prefix, primary, origin_type,
+             connector) = data
+            surname = models.Surname()
+            surname.surname = surname_text
+            surname.prefix = prefix
+            surname.primary = primary
+            surname.name_origin_type = models.get_type(models.NameOriginType, 
+                                                       origin_type)
+            surname.connector = connector
+            surname.name = name
+            surname.order = order
+            surname.save() 
+            order += 1
+
+    def add_note_list(self, obj, note_list):
+        for handle in note_list:
+            # Just the handle
+            try:
+                note = models.Note.objects.get(handle=handle)
+                self.add_note_ref(obj, note)
+            except:
+                print(("ERROR: Note does not exist: '%s'" % 
+                                      str(handle)), file=sys.stderr)
+    
+    def add_alternate_name_list(self, person, alternate_names):
+        for name in alternate_names:
+            if name:
+                self.add_name(person, name, False)
+    
+    def add_parent_family_list(self, person, parent_family_list):
+        for parent_family_data in parent_family_list:
+            self.add_parent_family(person, parent_family_data)
+    
+    def add_media_ref_list(self, person, media_list):
+        for media_data in media_list:
+            self.add_media_ref(person, media_data)
+    
+    def add_attribute_list(self, obj, attribute_list):
+        for attribute_data in attribute_list:
+            self.add_attribute(obj, attribute_data)
+    
+    def add_tag_list(self, obj, tag_list):
+        for tag_handle in tag_list:
+            try:
+                tag = models.Tag.objects.get(handle=tag_handle)
+            except:
+                print(("ERROR: Tag does not exist: '%s'" % 
+                                      str(tag_handle)), file=sys.stderr)
+            obj.tags.add(tag)
+    
+    def add_url_list(self, field, obj, url_list):
+        if not url_list: return None
+        count = 1
+        for url_data in url_list:
+            self.add_url(field, obj, url_data, count) 
+            count += 1
+            
+    def add_person_ref_list(self, obj, person_ref_list):
+        for person_ref_data in person_ref_list:
+            self.add_person_ref(obj, person_ref_data)
+    
+    def add_address_list(self, field, obj, address_list):
+        count = 1
+        for address_data in address_list:
+            self.add_address(field, obj, address_data, count)
+            count += 1
+    
+    def add_lds_list(self, field, obj, lds_ord_list):
+        count = 1
+        for ldsord in lds_ord_list:
+            lds = self.add_lds(field, obj, ldsord, count)
+            #obj.lds_list.add(lds)
+            #obj.save()
+            count += 1
+    
+    def add_repository_ref_list(self, obj, reporef_list):
+        for data in reporef_list:
+            self.add_repository_ref(obj, data)
+    
+    def add_place_ref_list(self, obj, placeref_list):
+        for data in placeref_list:
+            self.add_place_ref(obj, data)
+    
+    def add_family_ref_list(self, person, family_list):
+        for family_handle in family_list:
+            self.add_family_ref(person, family_handle) 
+    
+    def add_alt_name_list(self, place, alt_name_list):
+        print("FIXME: add alt_name_list!", alt_name_list)
+
+    ## Export reference objects:
+
+    def add_person_ref_default(self, obj, person, private=False, desc=None):
+        count = person.references.count()
+        person_ref = models.PersonRef(referenced_by=obj,
+                                  ref_object=person,
+                                  private=private,
+                                  order=count + 1,
+                                  description=desc)
+        person_ref.save()
+
+    def add_person_ref(self, obj, person_ref_data):
+        (private, 
+         citation_list,
+         note_list,
+         handle,
+         desc) = person_ref_data
+        try:
+            person = models.Person.objects.get(handle=handle)
+        except:
+            print(("ERROR: Person does not exist: '%s'" % 
+                                  str(handle)), file=sys.stderr)
+            return
+            
+        count = person.references.count()
+        person_ref = models.PersonRef(referenced_by=obj,
+                                  ref_object=person,
+                                  private=private,
+                                  order=count + 1,
+                                  description=desc)
+        person_ref.save()
+        self.add_note_list(person_ref, note_list)
+        self.add_citation_list(person_ref, citation_list)
+    
+    def add_note_ref(self, obj, note):
+        count = note.references.count()
+        note_ref = models.NoteRef(referenced_by=obj, 
+                              ref_object=note,
+                              private=False,
+                              order=count + 1)
+        note_ref.save()
+    
+    def add_media_ref_default(self, obj, media, private=False, role=None):
+        count = media.references.count()
+        if not role:
+            role = (0,0,0,0)
+        media_ref = models.MediaRef(referenced_by=obj, 
+                                ref_object=media,
+                                x1=role[0],
+                                y1=role[1],
+                                x2=role[2],
+                                y2=role[3],
+                                private=private,
+                                order=count + 1)
+        media_ref.save()
+
+    def add_media_ref(self, obj, media_ref_data):
+        (private, citation_list, note_list, attribute_list, 
+         ref, role) = media_ref_data
+        try:
+            media = models.Media.objects.get(handle=ref)
+        except:
+            print(("ERROR: Media does not exist: '%s'" % 
+                                  str(ref)), file=sys.stderr)
+            return
+        count = media.references.count()
+        if not role:
+            role = (0,0,0,0)
+        media_ref = models.MediaRef(referenced_by=obj, 
+                                ref_object=media,
+                                x1=role[0],
+                                y1=role[1],
+                                x2=role[2],
+                                y2=role[3],
+                                private=private,
+                                order=count + 1)
+        media_ref.save()
+        self.add_note_list(media_ref, note_list)
+        self.add_attribute_list(media_ref, attribute_list)
+        self.add_citation_list(media_ref, citation_list)
+    
+    def add_citation_ref_default(self, obj, citation, private=False):
+        object_type = ContentType.objects.get_for_model(obj)
+        count = models.CitationRef.objects.filter(object_id=obj.id,object_type=object_type).count()
+        citation_ref = models.CitationRef(private=private,
+                                          referenced_by=obj,
+                                          citation=citation,
+                                          order=count + 1)
+        citation_ref.save()
+
+    def add_citation_ref(self, obj, handle):
+        try:
+            citation = models.Citation.objects.get(handle=handle)
+        except:
+            print(("ERROR: Citation does not exist: '%s'" % 
+                                  str(handle)), file=sys.stderr)
+            return
+
+        object_type = ContentType.objects.get_for_model(obj)
+        count = models.CitationRef.objects.filter(object_id=obj.id,object_type=object_type).count()
+        citation_ref = models.CitationRef(private=False,
+                                          referenced_by=obj,
+                                          citation=citation,
+                                          order=count + 1)
+        citation_ref.save()
+
+    def add_citation(self, citation_data):
+        (handle, gid, date, page, confidence, source_handle, note_list,
+         media_list, attribute_list, changed, tag_list, private) = citation_data
+        citation = models.Citation(
+            handle=handle,
+            gramps_id=gid,
+            private=private, 
+            last_changed=todate(changed),
+            confidence=confidence, 
+            page=page)
+        citation.save(save_cache=False)
+
+    def add_citation_detail(self, citation_data):
+        (handle, gid, date, page, confidence, source_handle, note_list,
+         media_list, attribute_list, change, tag_list, private) = citation_data
+        try:
+            citation = models.Citation.objects.get(handle=handle)
+        except:
+            print(("ERROR: Citation does not exist: '%s'" % 
+                                  str(handle)), file=sys.stderr)
+            return
+        try:
+            source = models.Source.objects.get(handle=source_handle)
+        except:
+            print(("ERROR: Source does not exist: '%s'" % 
+                                  str(source_handle)), file=sys.stderr)
+            return
+        citation.source = source
+        self.add_date(citation, date)
+        citation.save(save_cache=False)
+        self.add_note_list(citation, note_list) 
+        self.add_media_ref_list(citation, media_list) 
+        self.add_citation_attribute_list(citation, attribute_list)
+        self.add_tag_list(citation, tag_list)
+        citation.save_cache()
+
+    def add_child_ref_default(self, obj, child, frel=1, mrel=1, private=False):
+        object_type = ContentType.objects.get_for_model(obj) # obj is family
+        count = models.ChildRef.objects.filter(object_id=obj.id,object_type=object_type).count()
+        child_ref = models.ChildRef(private=private,
+                                referenced_by=obj,
+                                ref_object=child,
+                                order=count + 1,
+                                father_rel_type=models.get_type(models.ChildRefType, frel),  # birth
+                                mother_rel_type=models.get_type(models.ChildRefType, mrel))
+        child_ref.save()
+
+    def add_child_ref(self, obj, data):
+        (private, citation_list, note_list, ref, frel, mrel) = data
+        try:
+            child = models.Person.objects.get(handle=ref)
+        except:
+            print(("ERROR: Person does not exist: '%s'" % 
+                                  str(ref)), file=sys.stderr)
+            return
+        object_type = ContentType.objects.get_for_model(obj)
+        count = models.ChildRef.objects.filter(object_id=obj.id,object_type=object_type).count()
+        child_ref = models.ChildRef(private=private,
+                                referenced_by=obj,
+                                ref_object=child,
+                                order=count + 1,
+                                father_rel_type=models.get_type(models.ChildRefType, frel),
+                                mother_rel_type=models.get_type(models.ChildRefType, mrel))
+        child_ref.save()
+        self.add_citation_list(child_ref, citation_list)
+        self.add_note_list(child_ref, note_list)
+    
+    def add_event_ref_default(self, obj, event, private=False, role=models.EventRoleType._DEFAULT):
+        object_type = ContentType.objects.get_for_model(obj)
+        count = models.EventRef.objects.filter(object_id=obj.id,object_type=object_type).count()
+        event_ref = models.EventRef(private=private,
+                                referenced_by=obj,
+                                ref_object=event,
+                                order=count + 1,
+                                role_type = models.get_type(models.EventRoleType, role))
+        event_ref.save()
+
+    def add_event_ref(self, obj, event_data):
+        (private, note_list, attribute_list, ref, role) = event_data
+        try:
+            event = models.Event.objects.get(handle=ref)
+        except:
+            print(("ERROR: Event does not exist: '%s'" % 
+                                  str(ref)), file=sys.stderr)
+            return
+        object_type = ContentType.objects.get_for_model(obj)
+        count = models.EventRef.objects.filter(object_id=obj.id,object_type=object_type).count()
+        event_ref = models.EventRef(private=private,
+                                referenced_by=obj,
+                                ref_object=event,
+                                order=count + 1,
+                                role_type = models.get_type(models.EventRoleType, role))
+        event_ref.save()
+        self.add_note_list(event_ref, note_list)
+        self.add_attribute_list(event_ref, attribute_list)
+
+    def add_repository_ref_default(self, obj, repository, private=False, call_number="", 
+                                   source_media_type=models.SourceMediaType._DEFAULT):
+        object_type = ContentType.objects.get_for_model(obj)
+        count = models.RepositoryRef.objects.filter(object_id=obj.id,object_type=object_type).count()
+        repos_ref = models.RepositoryRef(private=private,
+                                         referenced_by=obj,
+                                         call_number=call_number,
+                                         source_media_type=models.get_type(models.SourceMediaType,
+                                                                           source_media_type),
+                                         ref_object=repository,
+                                         order=count + 1)
+        repos_ref.save()
+    
+    def add_repository_ref(self, obj, reporef_data):
+        (note_list, 
+         ref,
+         call_number, 
+         source_media_type,
+         private) = reporef_data
+        try:
+            repository = models.Repository.objects.get(handle=ref)
+        except:
+            print(("ERROR: Repository does not exist: '%s'" % 
+                                  str(ref)), file=sys.stderr)
+            return
+        object_type = ContentType.objects.get_for_model(obj)
+        count = models.RepositoryRef.objects.filter(object_id=obj.id,object_type=object_type).count()
+        repos_ref = models.RepositoryRef(private=private,
+                                     referenced_by=obj,
+                                     call_number=call_number,
+                                     source_media_type=models.get_type(models.SourceMediaType,
+                                                                source_media_type),
+                                     ref_object=repository,
+                                     order=count + 1)
+        repos_ref.save()
+        self.add_note_list(repos_ref, note_list)
+    
+    def add_family_ref(self, obj, handle):
+        try:
+            family = models.Family.objects.get(handle=handle)
+        except:
+            print(("ERROR: Family does not exist: '%s'" % 
+                                  str(handle)), file=sys.stderr)
+            return
+        #obj.families.add(family)
+        pfo = models.MyFamilies(person=obj, family=family,
+                                order=len(models.MyFamilies.objects.filter(person=obj)) + 1)
+        pfo.save()
+        obj.save()
+    
+    ## Export individual objects:
+    
+    def add_source_attribute_list(self, source, attribute_list):
+        ## FIXME: dict to list
+        count = 1
+        #for key in datamap_dict:
+        #    value = datamap_dict[key]
+        #    datamap = models.SourceDatamap(key=key, value=value, order=count)
+        #    datamap.source = source
+        #    datamap.save()
+        #    count += 1
+    
+    def add_citation_attribute_list(self, citation, attribute_list):
+        ## FIXME: dict to list
+        count = 1
+        #for key in datamap_dict:
+        #    value = datamap_dict[key]
+        #    datamap = models.CitationDatamap(key=key, value=value, order=count)
+        #    datamap.citation = citation
+        #    datamap.save()
+        #    count += 1
+    
+    def add_lds(self, field, obj, data, order):
+        (lcitation_list, lnote_list, date, type, place_handle,
+         famc_handle, temple, status, private) = data
+        if place_handle:
+            try:
+                place = models.Place.objects.get(handle=place_handle)
+            except:
+                print(("ERROR: Place does not exist: '%s'" % 
+                                      str(place_handle)), file=sys.stderr)
+                place = None
+        else:
+            place = None
+        if famc_handle:
+            try:
+                famc = models.Family.objects.get(handle=famc_handle)
+            except:
+                print(("ERROR: Family does not exist: '%s'" % 
+                                      str(famc_handle)), file=sys.stderr)
+                famc = None
+        else:
+            famc = None
+        lds = models.Lds(lds_type = models.get_type(models.LdsType, type),
+                     temple=temple, 
+                     place=place,
+                     famc=famc,
+                     order=order,
+                     status = models.get_type(models.LdsStatus, status),
+                     private=private)
+        self.add_date(lds, date)
+        lds.save()
+        self.add_note_list(lds, lnote_list)
+        self.add_citation_list(lds, lcitation_list)
+        if field == "person":
+            lds.person = obj
+        elif field == "family":
+            lds.family = obj
+        else:
+            raise AttributeError("invalid field '%s' to attach lds" %
+                                 str(field))
+        lds.save()
+        return lds
+    
+    def add_address(self, field, obj, address_data, order):
+        (private, acitation_list, anote_list, date, location) = address_data
+        address = models.Address(private=private, order=order)
+        self.add_date(address, date)
+        address.save()
+        self.add_location("address", address, location, 1)
+        self.add_note_list(address, anote_list) 
+        self.add_citation_list(address, acitation_list)
+        if field == "person":
+            address.person = obj
+        elif field == "repository":
+            address.repository = obj
+        else:
+            raise AttributeError("invalid field '%s' to attach address" %
+                                 str(field))
+        address.save()
+        #obj.save()
+        #obj.addresses.add(address)
+        #obj.save()
+    
+    def add_attribute(self, obj, attribute_data):
+        (private, citation_list, note_list, the_type, value) = attribute_data
+        attribute_type = models.get_type(models.AttributeType, the_type)
+        attribute = models.Attribute(private=private,
+                                 attribute_of=obj,
+                                 attribute_type=attribute_type,
+                                 value=value)
+        attribute.save()
+        self.add_citation_list(attribute, citation_list)
+        self.add_note_list(attribute, note_list)
+        #obj.attributes.add(attribute)
+        #obj.save()
+    
+    def add_url(self, field, obj, url_data, order):
+        (private, path, desc, type) = url_data
+        url = models.Url(private=private,
+                     path=path,
+                     desc=desc,
+                     order=order,
+                     url_type=models.get_type(models.UrlType, type))
+        if field == "person":
+            url.person = obj
+        elif field == "repository":
+            url.repository = obj
+        elif field == "place":
+            url.place = obj
+        else:
+            raise AttributeError("invalid field '%s' to attach to url" %
+                                 str(field))
+        url.save()
+        #obj.url_list.add(url)
+        #obj.save()
+    
+    def add_place_ref_default(self, obj, place, date=None):
+        count = place.references.count()
+        object_type = ContentType.objects.get_for_model(obj)
+        count = models.PlaceRef.objects.filter(object_id=obj.id,
+                                               object_type=object_type).count()
+        place_ref = models.PlaceRef(referenced_by=obj,
+                                    ref_object=place,
+                                    order=count + 1)
+        self.add_date(obj, date)
+        place_ref.save()
+    
+    def add_place_ref(self, obj, data):
+        place_handle, date = data
+        if place_handle:
+            try:
+                place = models.Place.objects.get(handle=place_handle)
+            except:
+                print(("ERROR: Place does not exist: '%s'" % str(place_handle)), file=sys.stderr)
+                #from gramps.gen.utils.debug import format_exception
+                #print("".join(format_exception()), file=sys.stderr)
+                return
+        object_type = ContentType.objects.get_for_model(obj)
+        count = models.PlaceRef.objects.filter(object_id=obj.id,object_type=object_type).count()
+        place_ref = models.PlaceRef(referenced_by=obj, ref_object=place, order=count + 1)
+        place_ref.save()
+        self.add_date(place_ref, date)
+
+    def add_parent_family(self, person, parent_family_handle):
+        try:
+            family = models.Family.objects.get(handle=parent_family_handle)
+        except:
+            print(("ERROR: Family does not exist: '%s'" % 
+                                  str(parent_family_handle)), file=sys.stderr)
+            return
+        #person.parent_families.add(family)
+        pfo = models.MyParentFamilies(
+            person=person, 
+            family=family,
+            order=len(models.MyParentFamilies.objects.filter(person=person)) + 1)
+        pfo.save()
+        person.save()
+    
+    def add_date(self, obj, date):
+        if date is None: 
+            (calendar, modifier, quality, text, sortval, newyear) = \
+                (0, 0, 0, "", 0, 0)
+            day1, month1, year1, slash1 = 0, 0, 0, 0
+            day2, month2, year2, slash2 = 0, 0, 0, 0
+        else:
+            (calendar, modifier, quality, dateval, text, sortval, newyear) = date
+            if len(dateval) == 4:
+                day1, month1, year1, slash1 = dateval
+                day2, month2, year2, slash2 = 0, 0, 0, 0
+            elif len(dateval) == 8:
+                day1, month1, year1, slash1, day2, month2, year2, slash2 = dateval
+            else:
+                raise AttributeError("ERROR: dateval format '%s'" % str(dateval))
+        obj.calendar = calendar
+        obj.modifier = modifier
+        obj.quality = quality
+        obj.text = text
+        obj.sortval = sortval
+        obj.newyear = newyear
+        obj.day1 = day1
+        obj.month1 = month1
+        obj.year1 = year1
+        obj.slash1 = slash1
+        obj.day2 = day2
+        obj.month2 = month2
+        obj.year2 = year2
+        obj.slash2 = slash2
+    
+    def add_name(self, person, data, preferred):
+        if data:
+            (private, citation_list, note_list, date,
+             first_name, surname_list, suffix, title,
+             name_type, group_as, sort_as, 
+             display_as, call, nick, famnick) = data
+    
+            count = person.name_set.count()
+            name = models.Name()
+            name.order = count + 1
+            name.preferred = preferred
+            name.private = private
+            name.first_name = first_name
+            name.suffix = suffix
+            name.title = title
+            name.name_type = models.get_type(models.NameType, name_type)
+            name.group_as = group_as
+            name.sort_as = models.get_type(models.NameFormatType, sort_as)
+            name.display_as = models.get_type(models.NameFormatType, display_as)
+            name.call = call
+            name.nick = nick
+            name.famnick = famnick
+            # we know person exists
+            # needs to have an ID for key
+            name.person = person
+            self.add_date(name, date) 
+            name.save()
+            self.add_surname_list(name, surname_list)
+            self.add_note_list(name, note_list)
+            self.add_citation_list(name, citation_list)
+            #person.save()
+           
+    ## Export primary objects:
+    
+    def add_person(self, data):
+        # Unpack from the BSDDB:
+        (handle,        #  0
+         gid,          #  1
+         gender,             #  2
+         primary_name,       #  3
+         alternate_names,    #  4
+         death_ref_index,    #  5
+         birth_ref_index,    #  6
+         event_ref_list,     #  7
+         family_list,        #  8
+         parent_family_list, #  9
+         media_list,         # 10
+         address_list,       # 11
+         attribute_list,     # 12
+         url_list,               # 13
+         lds_ord_list,       # 14
+         pcitation_list,       # 15
+         pnote_list,         # 16
+         change,             # 17
+         tag_list,             # 18
+         private,           # 19
+         person_ref_list,    # 20
+         ) = data
+    
+        person = models.Person(handle=handle,
+                            gramps_id=gid,
+                            last_changed=todate(change),
+                            private=private,
+                            gender_type=models.get_type(models.GenderType, gender))
+        person.save(save_cache=False)
+
+    def add_person_detail(self, data):
+        # Unpack from the BSDDB:
+        (handle,        #  0
+         gid,          #  1
+         gender,             #  2
+         primary_name,       #  3
+         alternate_names,    #  4
+         death_ref_index,    #  5
+         birth_ref_index,    #  6
+         event_ref_list,     #  7
+         family_list,        #  8
+         parent_family_list, #  9
+         media_list,         # 10
+         address_list,       # 11
+         attribute_list,     # 12
+         url_list,               # 13
+         lds_ord_list,       # 14
+         pcitation_list,       # 15
+         pnote_list,         # 16
+         change,             # 17
+         tag_list,             # 18
+         private,           # 19
+         person_ref_list,    # 20
+         ) = data
+    
+        try:
+            person = models.Person.objects.get(handle=handle)
+        except:
+            print(("ERROR: Person does not exist: '%s'" % 
+                                  str(handle)), file=sys.stderr)
+            return
+        if primary_name:
+            self.add_name(person, primary_name, True)
+        self.add_alternate_name_list(person, alternate_names)
+        self.add_event_ref_list(person, event_ref_list)
+        self.add_family_ref_list(person, family_list) 
+        self.add_parent_family_list(person, parent_family_list)
+        self.add_media_ref_list(person, media_list)
+        self.add_note_list(person, pnote_list)
+        self.add_attribute_list(person, attribute_list)
+        self.add_url_list("person", person, url_list) 
+        self.add_person_ref_list(person, person_ref_list)
+        self.add_citation_list(person, pcitation_list)
+        self.add_address_list("person", person, address_list)
+        self.add_lds_list("person", person, lds_ord_list)
+        self.add_tag_list(person, tag_list)
+        # set person.birth and birth.death to correct events:
+
+        obj_type = ContentType.objects.get_for_model(person)
+        events = models.EventRef.objects.filter(
+            object_id=person.id, 
+            object_type=obj_type, 
+            ref_object__event_type__val=models.EventType.BIRTH).order_by("order")
+
+        all_events = self.get_event_ref_list(person)
+        if events:
+            person.birth = events[0].ref_object
+            person.birth_ref_index = lookup_role_index(models.EventType.BIRTH, all_events)
+
+        events = models.EventRef.objects.filter(
+            object_id=person.id, 
+            object_type=obj_type, 
+            ref_object__event_type__val=models.EventType.DEATH).order_by("order")
+        if events:
+            person.death = events[0].ref_object
+            person.death_ref_index = lookup_role_index(models.EventType.DEATH, all_events)
+        person.save()
+        return person
+
+    def save_note_markup(self, note, markup_list):
+        # delete any prexisting markup:
+        models.Markup.objects.filter(note=note).delete()
+        count = 1
+        for markup in markup_list:
+            markup_code, value, start_stop_list = markup
+            m = models.Markup(
+                note=note, 
+                order=count, 
+                styled_text_tag_type=models.get_type(models.StyledTextTagType,
+                                                     markup_code, 
+                                                     get_or_create=False),
+                string=value,
+                start_stop_list=str(start_stop_list))
+            m.save()
+    
+    def add_note(self, data):
+        # Unpack from the BSDDB:
+        (handle, gid, styled_text, format, note_type,
+         change, tag_list, private) = data
+        text, markup_list = styled_text
+        n = models.Note(handle=handle,
+                        gramps_id=gid,
+                        last_changed=todate(change),
+                        private=private,
+                        preformatted=format,
+                        text=text,
+                        note_type=models.get_type(models.NoteType, note_type))
+        n.save(save_cache=False)
+        self.save_note_markup(n, markup_list)
+    
+    def add_note_detail(self, data):
+        # Unpack from the BSDDB:
+        (handle, gid, styled_text, format, note_type,
+         change, tag_list, private) = data
+        note = models.Note.objects.get(handle=handle)
+        note.save(save_cache=False)
+        self.add_tag_list(note, tag_list)
+        note.save_cache()
+
+    def add_family(self, data):
+        # Unpack from the BSDDB:
+        (handle, gid, father_handle, mother_handle,
+         child_ref_list, the_type, event_ref_list, media_list,
+         attribute_list, lds_seal_list, citation_list, note_list,
+         change, tag_list, private) = data
+    
+        family = models.Family(handle=handle, gramps_id=gid, 
+                               family_rel_type = models.get_type(models.FamilyRelType, the_type),
+                               last_changed=todate(change), 
+                               private=private)
+        family.save(save_cache=False)
+
+    def add_family_detail(self, data):
+        # Unpack from the BSDDB:
+        (handle, gid, father_handle, mother_handle,
+         child_ref_list, the_type, event_ref_list, media_list,
+         attribute_list, lds_seal_list, citation_list, note_list,
+         change, tag_list, private) = data
+    
+        try:
+            family = models.Family.objects.get(handle=handle)
+        except:
+            print(("ERROR: Family does not exist: '%s'" % 
+                                  str(handle)), file=sys.stderr)
+            return
+        # father_handle and/or mother_handle can be None
+        if father_handle:
+            try:
+                family.father = models.Person.objects.get(handle=father_handle)
+            except:
+                print(("ERROR: Father does not exist: '%s'" % 
+                                      str(father_handle)), file=sys.stderr)
+                family.father = None
+        if mother_handle:
+            try:
+                family.mother = models.Person.objects.get(handle=mother_handle)
+            except:
+                print(("ERROR: Mother does not exist: '%s'" % 
+                                      str(mother_handle)), file=sys.stderr)
+                family.mother = None
+        family.save(save_cache=False)
+        self.add_child_ref_list(family, child_ref_list)
+        self.add_note_list(family, note_list)
+        self.add_attribute_list(family, attribute_list)
+        self.add_citation_list(family, citation_list)
+        self.add_media_ref_list(family, media_list)
+        self.add_event_ref_list(family, event_ref_list)
+        self.add_lds_list("family", family, lds_seal_list)
+        self.add_tag_list(family, tag_list)
+        family.save_cache()
+        
+    def add_source(self, data):
+        (handle, gid, title,
+         author, pubinfo,
+         note_list,
+         media_list,
+         abbrev,
+         change, attribute_list,
+         reporef_list,
+         tag_list,
+         private) = data
+        source = models.Source(handle=handle, gramps_id=gid, title=title,
+                               author=author, pubinfo=pubinfo, abbrev=abbrev,
+                               last_changed=todate(change), private=private)
+        source.save(save_cache=False)
+
+    def add_source_detail(self, data):
+        (handle, gid, title,
+         author, pubinfo,
+         note_list,
+         media_list,
+         abbrev,
+         change, attribute_list,
+         reporef_list,
+         tag_list,
+         private) = data
+        try:
+            source = models.Source.objects.get(handle=handle)
+        except:
+            print(("ERROR: Source does not exist: '%s'" % 
+                                  str(handle)), file=sys.stderr)
+            return
+        source.save(save_cache=False)
+        self.add_note_list(source, note_list) 
+        self.add_media_ref_list(source, media_list)
+        self.add_source_attribute_list(source, attribute_list)
+        self.add_repository_ref_list(source, reporef_list)
+        self.add_tag_list(source, tag_list)
+        source.save_cache()
+    
+    def add_repository(self, data):
+        (handle, gid, the_type, name, note_list,
+         address_list, url_list, change, tag_list, private) = data
+    
+        repository = models.Repository(handle=handle,
+                                       gramps_id=gid,
+                                       last_changed=todate(change), 
+                                       private=private,
+                                       repository_type=models.get_type(models.RepositoryType, the_type),
+                                       name=name)
+        repository.save(save_cache=False)
+
+    def add_repository_detail(self, data):
+        (handle, gid, the_type, name, note_list,
+         address_list, url_list, change, tag_list, private) = data
+        try:
+            repository = models.Repository.objects.get(handle=handle)
+        except:
+            print(("ERROR: Repository does not exist: '%s'" % 
+                                  str(handle)), file=sys.stderr)
+            return
+        repository.save(save_cache=False)
+        self.add_note_list(repository, note_list)
+        self.add_url_list("repository", repository, url_list)
+        self.add_address_list("repository", repository, address_list)
+        self.add_tag_list(repository, tag_list)
+        repository.save_cache()
+    
+    def add_location(self, field, obj, location_data, order):
+        # location now has 8 items
+        # street, locality, city, county, state,
+        # country, postal, phone, parish
+
+        if location_data == None: return
+        if len(location_data) == 8:
+            (street, locality, city, county, state, country, postal, phone) = location_data
+            parish = None
+        elif len(location_data) == 2:
+            ((street, locality, city, county, state, country, postal, phone), parish) = location_data
+        else:
+            print(("ERROR: unknown location: '%s'" % 
+                                  str(location_data)), file=sys.stderr)
+            (street, locality, city, county, state, country, postal, phone, parish) = \
+                ("", "", "", "", "", "", "", "", "")
+        location = models.Location(street = street,
+                                   locality = locality,
+                               city = city,
+                               county = county,
+                               state = state,
+                               country = country,
+                               postal = postal,
+                               phone = phone,
+                               parish = parish,
+                               order = order)
+        if field == "address":
+            location.address = obj
+        elif field == "place":
+            location.place = obj
+        else:
+            raise AttributeError("invalid field '%s' to attach to location" %
+                                 str(field))
+        location.save()
+        #obj.locations.add(location)
+        #obj.save()
+    
+    def add_place(self, data):
+        ## ('cef246c95c132bcf6a0255d4d17', 'P0036', 'Santa Clara Co., CA, USA', '', '', [('cef243fb5634559442323368f63', None)], 'Santa Clara Co.', [], (3, ''), '', [], [], [], [], [], 1422124781, [], False)
+        (handle, gid, title, long, lat,
+         place_ref_list,
+         name,
+         alt_name_list,
+         place_type,
+         code,
+         alt_location_list,
+         url_list,
+         media_list,
+         citation_list,
+         note_list,
+         change, 
+         tag_list,
+         private) = data
+        place = models.Place(
+            handle=handle, 
+            gramps_id=gid, 
+            title=title,
+            long=long, 
+            lat=lat, 
+            name=name,
+            place_type=models.get_type(models.PlaceType, place_type), 
+            code=code, 
+            last_changed=todate(change),
+            private=private)
+        try:
+            place.save(save_cache=False)
+        except:
+            print("FIXME: error in saving place")
+
+    def add_place_detail(self, data):
+        (handle, gid, title, long, lat,
+         place_ref_list,
+         name,
+         alt_name_list,
+         place_type,
+         code,
+         alt_location_list,
+         url_list,
+         media_list,
+         citation_list,
+         note_list,
+         change, 
+         tag_list,
+         private) = data
+        try:
+            place = models.Place.objects.get(handle=handle)
+        except:
+            print(("ERROR: Place does not exist: '%s'" % 
+                                  str(handle)), file=sys.stderr)
+            return
+        place.save(save_cache=False)
+        self.add_url_list("place", place, url_list)
+        self.add_media_ref_list(place, media_list)
+        self.add_citation_list(place, citation_list)
+        self.add_note_list(place, note_list) 
+        self.add_tag_list(place, tag_list)
+        self.add_place_ref_list(place, place_ref_list)
+        self.add_alt_name_list(place, alt_name_list)
+        count = 1
+        for loc_data in alt_location_list:
+            self.add_location("place", place, loc_data, count)
+            count + 1
+        place.save_cache()
+
+    def add_tag(self, data):
+        (handle,
+         name,
+         color,
+         priority,
+         change) = data
+        tag = models.Tag(handle=handle,
+                         gramps_id=create_id(),
+                         name=name,
+                         color=color,
+                         priority=priority,
+                         last_changed=todate(change))
+        tag.save(save_cache=False)
+    
+    def add_tag_detail(self, data):
+        (handle,
+         name,
+         color,
+         priority,
+         change) = data
+        tag = models.Tag.objects.get(handle=handle)
+        tag.save()
+
+    def add_media(self, data):
+        (handle, gid, path, mime, desc,
+         checksum,
+         attribute_list,
+         citation_list,
+         note_list,
+         change,
+         date,
+         tag_list,
+         private) = data
+        media = models.Media(handle=handle, gramps_id=gid,
+                             path=path, mime=mime, checksum=checksum,
+                             desc=desc, last_changed=todate(change),
+                             private=private)
+        self.add_date(media, date)
+        media.save(save_cache=False)
+    
+    def add_media_detail(self, data):
+        (handle, gid, path, mime, desc,
+         checksum,
+         attribute_list,
+         citation_list,
+         note_list,
+         change,
+         date,
+         tag_list,
+         private) = data
+        try:
+            media = models.Media.objects.get(handle=handle)
+        except:
+            print(("ERROR: Media does not exist: '%s'" % 
+                                  str(handle)), file=sys.stderr)
+            return
+        media.save(save_cache=False)
+        self.add_note_list(media, note_list) 
+        self.add_citation_list(media, citation_list)
+        self.add_attribute_list(media, attribute_list)
+        self.add_tag_list(media, tag_list)
+        media.save_cache()
+    
+    def add_event(self, data):
+        (handle, gid, the_type, date, description, place_handle, 
+         citation_list, note_list, media_list, attribute_list,
+         change, tag_list, private) = data
+        event = models.Event(handle=handle,
+                             gramps_id=gid, 
+                             event_type=models.get_type(models.EventType, the_type),
+                             private=private,
+                             description=description,
+                             last_changed=todate(change))
+        self.add_date(event, date)
+        event.save(save_cache=False)
+
+    def add_event_detail(self, data):
+        (handle, gid, the_type, date, description, place_handle, 
+         citation_list, note_list, media_list, attribute_list,
+         change, tag_list, private) = data
+        try:
+            event = models.Event.objects.get(handle=handle)
+        except:
+            print(("ERROR: Event does not exist: '%s'" % 
+                                  str(handle)), file=sys.stderr)
+            return
+        try:
+            place = models.Place.objects.get(handle=place_handle)
+        except:
+            place = None
+            print(("ERROR: Place does not exist: '%s'" % 
+                                  str(place_handle)), file=sys.stderr)
+        event.place = place
+        event.save(save_cache=False)
+        self.add_note_list(event, note_list)
+        self.add_attribute_list(event, attribute_list)
+        self.add_media_ref_list(event, media_list)
+        self.add_citation_list(event, citation_list)
+        self.add_tag_list(event, tag_list)
+        event.save_cache()
+
+    def get_raw(self, item):
+        """
+        Build and return the raw, serialized data of an object.
+        """
+        if isinstance(item, models.Person):
+            raw = self.get_person(item)
+        elif isinstance(item, models.Family):
+            raw = self.get_family(item)
+        elif isinstance(item, models.Place):
+            raw = self.get_place(item)
+        elif isinstance(item, models.Media):
+            raw = self.get_media(item)
+        elif isinstance(item, models.Source):
+            raw = self.get_source(item)
+        elif isinstance(item, models.Citation):
+            raw = self.get_citation(item)
+        elif isinstance(item, models.Repository):
+            raw = self.get_repository(item)
+        elif isinstance(item, models.Note):
+            raw = self.get_note(item)
+        elif isinstance(item, models.Event):
+            raw = self.get_event(item)
+        else:
+            raise Exception("Don't know how to get raw '%s'" % type(item))
+        return raw
+
+    def check_caches(self, callback=None):
+        """
+        Call this to check the caches for all primary models.
+        """
+        if not isinstance(callback, collections.Callable): 
+            callback = lambda percent: None # dummy
+
+        callback(0)
+        count = 0.0
+        total = (self.Note.all().count() + 
+                 self.Person.all().count() +
+                 self.Event.all().count() + 
+                 self.Family.all().count() +
+                 self.Repository.all().count() +
+                 self.Place.all().count() +
+                 self.Media.all().count() +
+                 self.Source.all().count() +
+                 self.Citation.all().count() +
+                 self.Tag.all().count())
+
+        for item in self.Note.all():
+            raw = self.get_note(item)
+            check_diff(item, raw)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Person.all():
+            raw = self.get_person(item)
+            check_diff(item, raw)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Family.all():
+            raw = self.get_family(item)
+            check_diff(item, raw)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Source.all():
+            raw = self.get_source(item)
+            check_diff(item, raw)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Event.all():
+            raw = self.get_event(item)
+            check_diff(item, raw)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Repository.all():
+            raw = self.get_repository(item)
+            check_diff(item, raw)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Place.all():
+            raw = self.get_place(item)
+            check_diff(item, raw)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Media.all():
+            raw = self.get_media(item)
+            check_diff(item, raw)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Citation.all():
+            raw = self.get_citation(item)
+            check_diff(item, raw)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Tag.all():
+            raw = self.get_tag(item)
+            check_diff(item, raw)
+            count += 1
+        callback(100)
+
+    def check_families(self):
+        """
+        Check family structures.
+        """
+        for family in self.Family.all():
+            if family.mother:
+                if not family in family.mother.families.all():
+                    print("Mother not in family", mother, family)
+            if family.father:
+                if not family in family.father.families.all():
+                    print("Father not in family", mother, family)
+            for child in family.get_children():
+                if family not in child.parent_families.all():
+                    print("Child not in family", child, family)
+        for person in self.Person.all():
+            for family in person.families.all():
+                if person not in [family.mother, family.father]:
+                    print("Spouse not in family", person, family)
+            for family in person.parent_families.all():
+                if person not in family.get_children():
+                    print("Child not in family", person, family)
+
+    def is_public(self, obj, objref):
+        """
+        Returns whether or not an item is "public", and the reason
+        why/why not.
+
+        @param obj - an instance of any Primary object
+        @param objref - one of the PrimaryRef.objects 
+        @return - a tuple containing a boolean (public?) and reason.
+
+        There are three reasons why an item might not be public:
+           1) The item itself is private.
+           2) The item is referenced by a living Person.
+           3) The item is referenced by some other private item.
+        """
+        # If it is private, then no:
+        if obj.private:
+            return (False, "It is marked private.")
+        elif hasattr(obj, "probably_alive") and obj.probably_alive:
+            return (False, "It is marked probaby alive.")
+        elif hasattr(obj, "mother") and obj.mother:
+            public, reason = self.is_public(obj.mother, self.PersonRef)
+            if not public:
+                return public, reason
+        elif hasattr(obj, "father") and obj.father:
+            public, reason = self.is_public(obj.father, self.PersonRef)
+            if not public:
+                return public, reason
+        # FIXME: what about Associations... anything else? Check PrivateProxy
+        if objref:
+            if hasattr(objref.model, "ref_object"):
+                obj_ref_list = objref.filter(ref_object=obj)
+            elif hasattr(objref.model, "citation"):
+                obj_ref_list = objref.filter(citation=obj)
+            else:
+                raise Exception("objref '%s' needs a ref for '%s'" % (objref.model, obj))
+            for reference in obj_ref_list:
+                ref_from_class = reference.object_type.model_class()
+                item = None
+                try:
+                    item = ref_from_class.objects.get(id=reference.object_id)
+                except:
+                    print("Warning: Corrupt reference: %s" % str(reference))
+                    continue
+                # If it is linked to by someone alive? public = False
+                if hasattr(item, "probably_alive") and item.probably_alive:
+                    return (False, "It is referenced by someone who is probaby alive.")
+                # If it is linked to by something private? public = False
+                elif item.private:
+                    return (False, "It is referenced by an item which is marked private.")
+        return (True, "It is visible to the public.")
+
+    def update_public(self, obj, save=True):
+        """
+        >>> dji.update_public(event)
+
+        Given an Event or other instance, update the event's public
+        status, or any event referenced to by the instance.
+
+        For example, if a person is found to be alive, then the
+        referenced events should be marked not public (public = False).
+
+        """
+        from gramps.webapp.utils import probably_alive
+        if obj.__class__.__name__ == "Event":
+            objref = self.EventRef
+        elif obj.__class__.__name__ == "Person":
+            objref = self.PersonRef 
+        elif obj.__class__.__name__ == "Note":
+            objref = self.NoteRef
+        elif obj.__class__.__name__ == "Repository":
+            objref = self.RepositoryRef
+        elif obj.__class__.__name__ == "Citation":
+            objref = self.CitationRef
+        elif obj.__class__.__name__ == "Media":
+            objref = self.MediaRef
+        elif  obj.__class__.__name__ == "Place": # no need for dependency
+            objref = None
+        elif  obj.__class__.__name__ == "Source": # no need for dependency
+            objref = None
+        elif  obj.__class__.__name__ == "Family":
+            objref = self.ChildRef # correct?
+        else:
+            raise Exception("Can't compute public of type '%s'" % str(obj))
+        public, reason = self.is_public(obj, objref) # correct?
+        # Ok, update, if needed:
+        if obj.public != public:
+            obj.public = public
+            if save:
+                print("Updating public:", obj.__class__.__name__, obj.gramps_id)
+                obj.save()
+                #log = self.Log()
+                #log.referenced_by = obj
+                #log.object_id = obj.id
+                #log.object_type = obj_type
+                #log.log_type = "update public status"
+                #log.reason = reason
+                #log.order = 0
+                #log.save()
+
+    def update_publics(self, callback=None):
+        """
+        Call this to update probably_alive for all primary models.
+        """
+        if not isinstance(callback, collections.Callable): 
+            callback = lambda percent: None # dummy
+
+        callback(0)
+        count = 0.0
+        total = (self.Note.all().count() + 
+                 self.Person.all().count() +
+                 self.Event.all().count() + 
+                 self.Family.all().count() +
+                 self.Repository.all().count() +
+                 self.Place.all().count() +
+                 self.Media.all().count() +
+                 self.Source.all().count() +
+                 self.Citation.all().count())
+
+        for item in self.Note.all():
+            self.update_public(item)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Person.all():
+            self.update_public(item)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Family.all():
+            self.update_public(item)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Source.all():
+            self.update_public(item)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Event.all():
+            self.update_public(item)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Repository.all():
+            self.update_public(item)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Place.all():
+            self.update_public(item)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Media.all():
+            self.update_public(item)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+        for item in self.Citation.all():
+            self.update_public(item)
+            count += 1
+        callback(100 * (count/total if total else 0))
+
+    def update_probably_alive(self, callback=None):
+        """
+        Call this to update primary_alive for people.
+        """
+        from gramps.webapp.utils import probably_alive
+        if not isinstance(callback, collections.Callable): 
+            callback = lambda percent: None # dummy
+        callback(0)
+        count = 0.0
+        total = self.Person.all().count()
+        for item in self.Person.all():
+            pa = probably_alive(item.handle)
+            if pa != item.probably_alive:
+                print("Updating probably_alive")
+                item.probably_alive = pa
+                item.save()
+            count += 1
+        callback(100 * (count/total if total else 0))
diff --git a/gramps/plugins/database/djangodb.gpr.py b/gramps/plugins/database/djangodb.gpr.py
new file mode 100644
index 000000000..57b30f2f5
--- /dev/null
+++ b/gramps/plugins/database/djangodb.gpr.py
@@ -0,0 +1,31 @@
+#
+# Gramps - a GTK+/GNOME based genealogy program
+#
+# Copyright (C) 2015 Douglas Blank <doug.blank@gmail.com>
+#
+# 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+plg = newplugin()
+plg.id    = 'djangodb'
+plg.name  = _("Django Database Backend")
+plg.name_accell  = _("_Django Database Backend")
+plg.description =  _("Django Object Relational Model Database Backend")
+plg.version = '1.0'
+plg.gramps_target_version = "4.2"
+plg.status = STABLE
+plg.fname = 'djangodb.py'
+plg.ptype = DATABASE
+plg.databaseclass = 'DbDjango'
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
new file mode 100644
index 000000000..adb28dc54
--- /dev/null
+++ b/gramps/plugins/database/djangodb.py
@@ -0,0 +1,2036 @@
+# Gramps - a GTK+/GNOME based genealogy program
+#
+# Copyright (C) 2009         Douglas S. Blank <doug.blank@gmail.com>
+#
+# 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+""" Implements a Db interface """
+
+#------------------------------------------------------------------------
+#
+# Python Modules
+#
+#------------------------------------------------------------------------
+import sys
+import time
+import re
+import base64
+import pickle
+import os
+
+#------------------------------------------------------------------------
+#
+# Gramps Modules
+#
+#------------------------------------------------------------------------
+import gramps
+from gramps.gen.lib import (Person, Family, Event, Place, Repository, 
+                            Citation, Source, Note, MediaObject, Tag, 
+                            Researcher)
+from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn
+from gramps.gen.utils.callback import Callback
+from gramps.gen.updatecallback import UpdateCallback
+from gramps.gen.db import (PERSON_KEY,
+                    FAMILY_KEY,
+                    CITATION_KEY,
+                    SOURCE_KEY,
+                    EVENT_KEY,
+                    MEDIA_KEY,
+                    PLACE_KEY,
+                    REPOSITORY_KEY,
+                    NOTE_KEY)
+from gramps.gen.utils.id import create_id
+from django.db import transaction
+
+class Environment(object):
+    """
+    Implements the Environment API.
+    """
+    def __init__(self, db):
+        self.db = db
+
+    def txn_begin(self):
+        return DjangoTxn("DbDjango Transaction", self.db)
+
+class Table(object):
+    """
+    Implements Table interface.
+    """
+    def __init__(self, funcs):
+        self.funcs = funcs
+
+    def cursor(self):
+        """
+        Returns a Cursor for this Table.
+        """
+        return self.funcs["cursor_func"]()
+
+    def put(key, data, txn=None):
+        self[key] = data
+
+class Map(dict):
+    """
+    Implements the map API for person_map, etc.
+    
+    Takes a Table() as argument.
+    """
+    def __init__(self, tbl, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.db = tbl
+
+class MetaCursor(object):
+    def __init__(self):
+        pass
+    def __enter__(self):
+        return self
+    def __iter__(self):
+        return self.__next__()
+    def __next__(self):
+        yield None
+    def __exit__(self, *args, **kwargs):
+        pass
+    def iter(self):
+        yield None
+    def first(self):
+        self._iter = self.__iter__()
+        return self.next()
+    def next(self):
+        try:
+            return next(self._iter)
+        except:
+            return None
+    def close(self):
+        pass
+
+class Cursor(object):
+    def __init__(self, model, func):
+        self.model = model
+        self.func = func
+    def __enter__(self):
+        return self
+    def __iter__(self):
+        return self.__next__()
+    def __next__(self):
+        for item in self.model.all():
+            yield (bytes(item.handle, "utf-8"), self.func(item.handle))
+    def __exit__(self, *args, **kwargs):
+        pass
+    def iter(self):
+        for item in self.model.all():
+            yield (bytes(item.handle, "utf-8"), self.func(item.handle))
+        yield None
+    def first(self):
+        self._iter = self.__iter__()
+        return self.next()
+    def next(self):
+        try:
+            return next(self._iter)
+        except:
+            return None
+    def close(self):
+        pass
+
+class Bookmarks(object):
+    def __init__(self):
+        self.handles = []
+    def get(self):
+        return self.handles
+    def append(self, handle):
+        self.handles.append(handle)
+
+class DjangoTxn(DbTxn):
+    def __init__(self, message, db, table=None):
+        DbTxn.__init__(self, message, db)
+        self.table = table
+
+    def get(self, key, default=None, txn=None, **kwargs):
+        """
+        Returns the data object associated with key
+        """
+        try:
+            return self.table.objects(handle=key)
+        except:
+            if txn and key in txn:
+                return txn[key]
+            else:
+                return None
+
+    def put(self, handle, new_data, txn):
+        """
+        """
+        txn[handle] = new_data
+
+    def commit(self):
+        pass
+
+class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
+    """
+    A Gramps Database Backend. This replicates the grampsdb functions.
+    """
+    # Set up dictionary for callback signal handler
+    # ---------------------------------------------
+    # 1. Signals for primary objects
+    __signals__ = dict((obj+'-'+op, signal)
+                       for obj in
+                       ['person', 'family', 'event', 'place',
+                        'source', 'citation', 'media', 'note', 'repository', 'tag']
+                       for op, signal in zip(
+                               ['add',   'update', 'delete', 'rebuild'],
+                               [(list,), (list,),  (list,),   None]
+                       )
+                   )
+    
+    # 2. Signals for long operations
+    __signals__.update(('long-op-'+op, signal) for op, signal in zip(
+        ['start',  'heartbeat', 'end'],
+        [(object,), None,       None]
+        ))
+
+    # 3. Special signal for change in home person
+    __signals__['home-person-changed'] = None
+
+    # 4. Signal for change in person group name, parameters are
+    __signals__['person-groupname-rebuild'] = (str, str)
+
+    def __init__(self, directory=None):
+        DbReadBase.__init__(self)
+        DbWriteBase.__init__(self)
+        Callback.__init__(self)
+        self._tables = {
+            'Person':
+                {
+                "handle_func": self.get_person_from_handle, 
+                "gramps_id_func": self.get_person_from_gramps_id,
+                "class_func": gramps.gen.lib.Person,
+                "cursor_func": self.get_person_cursor,
+                "handles_func": self.get_person_handles,
+                "iter_func": self.iter_people,
+                },
+            'Family':
+                {
+                "handle_func": self.get_family_from_handle, 
+                "gramps_id_func": self.get_family_from_gramps_id,
+                "class_func": gramps.gen.lib.Family,
+                "cursor_func": self.get_family_cursor,
+                "handles_func": self.get_family_handles,
+                "iter_func": self.iter_families,
+                },
+            'Source':
+                {
+                "handle_func": self.get_source_from_handle, 
+                "gramps_id_func": self.get_source_from_gramps_id,
+                "class_func": gramps.gen.lib.Source,
+                "cursor_func": self.get_source_cursor,
+                "handles_func": self.get_source_handles,
+                "iter_func": self.iter_sources,
+                },
+            'Citation':
+                {
+                "handle_func": self.get_citation_from_handle, 
+                "gramps_id_func": self.get_citation_from_gramps_id,
+                "class_func": gramps.gen.lib.Citation,
+                "cursor_func": self.get_citation_cursor,
+                "handles_func": self.get_citation_handles,
+                "iter_func": self.iter_citations,
+                },
+            'Event':
+                {
+                "handle_func": self.get_event_from_handle, 
+                "gramps_id_func": self.get_event_from_gramps_id,
+                "class_func": gramps.gen.lib.Event,
+                "cursor_func": self.get_event_cursor,
+                "handles_func": self.get_event_handles,
+                "iter_func": self.iter_events,
+                },
+            'Media':
+                {
+                "handle_func": self.get_object_from_handle, 
+                "gramps_id_func": self.get_object_from_gramps_id,
+                "class_func": gramps.gen.lib.MediaObject,
+                "cursor_func": self.get_media_cursor,
+                "handles_func": self.get_media_object_handles,
+                "iter_func": self.iter_media_objects,
+                },
+            'Place':
+                {
+                "handle_func": self.get_place_from_handle, 
+                "gramps_id_func": self.get_place_from_gramps_id,
+                "class_func": gramps.gen.lib.Place,
+                "cursor_func": self.get_place_cursor,
+                "handles_func": self.get_place_handles,
+                "iter_func": self.iter_places,
+                },
+            'Repository':
+                {
+                "handle_func": self.get_repository_from_handle, 
+                "gramps_id_func": self.get_repository_from_gramps_id,
+                "class_func": gramps.gen.lib.Repository,
+                "cursor_func": self.get_repository_cursor,
+                "handles_func": self.get_repository_handles,
+                "iter_func": self.iter_repositories,
+                },
+            'Note':
+                {
+                "handle_func": self.get_note_from_handle, 
+                "gramps_id_func": self.get_note_from_gramps_id,
+                "class_func": gramps.gen.lib.Note,
+                "cursor_func": self.get_note_cursor,
+                "handles_func": self.get_note_handles,
+                "iter_func": self.iter_notes,
+                },
+            'Tag':
+                {
+                "handle_func": self.get_tag_from_handle, 
+                "gramps_id_func": None,
+                "class_func": gramps.gen.lib.Tag,
+                "cursor_func": self.get_tag_cursor,
+                "handles_func": self.get_tag_handles,
+                "iter_func": self.iter_tags,
+                },
+            }
+        # skip GEDCOM cross-ref check for now:
+        self.set_feature("skip-check-xref", True)
+        self.readonly = False
+        self.db_is_open = True
+        self.name_formats = []
+        self.bookmarks = Bookmarks()
+        self.undo_callback = None
+        self.redo_callback = None
+        self.undo_history_callback = None
+        self.modified   = 0
+        self.txn = DjangoTxn("DbDjango Transaction", self)
+        self.transaction = None
+        # Import cache for gedcom import, uses transactions, and
+        # two step adding of objects.
+        self.import_cache = {}
+        self.use_import_cache = False
+        self.use_db_cache = True
+        self._directory = directory
+        if directory:
+            self.load(directory)
+
+    def load(self, directory, pulse_progress=None, mode=None):
+        self._directory = directory
+        from django.conf import settings
+        default_settings = {}
+        settings_file = os.path.join(directory, "default_settings.py")
+        with open(settings_file) as f:
+            code = compile(f.read(), settings_file, 'exec')
+            exec(code, globals(), default_settings)
+
+        class Module(object):
+            def __init__(self, dictionary):
+                self.dictionary = dictionary
+            def __getattr__(self, item):
+                return self.dictionary[item]
+
+        try:
+            settings.configure(Module(default_settings))
+        except RuntimeError:
+            # already configured; ignore
+            pass
+
+        import django
+        django.setup()
+
+        from django_support.libdjango import DjangoInterface
+        self.dji = DjangoInterface()
+        self.family_bookmarks = Bookmarks()
+        self.event_bookmarks = Bookmarks()
+        self.place_bookmarks = Bookmarks()
+        self.citation_bookmarks = Bookmarks()
+        self.source_bookmarks = Bookmarks()
+        self.repo_bookmarks = Bookmarks()
+        self.media_bookmarks = Bookmarks()
+        self.note_bookmarks = Bookmarks()
+        self.set_person_id_prefix('I%04d')
+        self.set_object_id_prefix('O%04d')
+        self.set_family_id_prefix('F%04d')
+        self.set_citation_id_prefix('C%04d')
+        self.set_source_id_prefix('S%04d')
+        self.set_place_id_prefix('P%04d')
+        self.set_event_id_prefix('E%04d')
+        self.set_repository_id_prefix('R%04d')
+        self.set_note_id_prefix('N%04d')
+        # ----------------------------------
+        self.id_trans  = DjangoTxn("ID Transaction", self, self.dji.Person)
+        self.fid_trans = DjangoTxn("FID Transaction", self, self.dji.Family)
+        self.pid_trans = DjangoTxn("PID Transaction", self, self.dji.Place)
+        self.cid_trans = DjangoTxn("CID Transaction", self, self.dji.Citation)
+        self.sid_trans = DjangoTxn("SID Transaction", self, self.dji.Source)
+        self.oid_trans = DjangoTxn("OID Transaction", self, self.dji.Media)
+        self.rid_trans = DjangoTxn("RID Transaction", self, self.dji.Repository)
+        self.nid_trans = DjangoTxn("NID Transaction", self, self.dji.Note)
+        self.eid_trans = DjangoTxn("EID Transaction", self, self.dji.Event)
+        self.cmap_index = 0
+        self.smap_index = 0
+        self.emap_index = 0
+        self.pmap_index = 0
+        self.fmap_index = 0
+        self.lmap_index = 0
+        self.omap_index = 0
+        self.rmap_index = 0
+        self.nmap_index = 0
+        self.env = Environment(self)
+        self.person_map = Map(Table(self._tables["Person"]))
+        self.family_map = Map(Table(self._tables["Family"]))
+        self.place_map  = Map(Table(self._tables["Place"]))
+        self.citation_map = Map(Table(self._tables["Citation"]))
+        self.source_map = Map(Table(self._tables["Source"]))
+        self.repository_map  = Map(Table(self._tables["Repository"]))
+        self.note_map = Map(Table(self._tables["Note"]))
+        self.media_map  = Map(Table(self._tables["Media"]))
+        self.event_map  = Map(Table(self._tables["Event"]))
+        self.tag_map  = Map(Table(self._tables["Tag"]))
+        self.metadata   = Map(Table({"cursor_func": lambda: MetaCursor()}))
+        self.name_group = {}
+        self.event_names = set()
+        self.individual_attributes = set()
+        self.family_attributes = set()
+        self.source_attributes = set()
+        self.child_ref_types = set()
+        self.family_rel_types = set()
+        self.event_role_names = set()
+        self.name_types = set()
+        self.origin_types = set()
+        self.repository_types = set()
+        self.note_types = set()
+        self.source_media_types = set()
+        self.url_types = set()
+        self.media_attributes = set()
+        self.place_types = set()
+
+    def prepare_import(self):
+        """
+        DbDjango does not commit data on gedcom import, but saves them
+        for later commit.
+        """
+        self.use_import_cache = True
+        self.import_cache = {}
+
+    @transaction.atomic
+    def commit_import(self):
+        """
+        Commits the items that were queued up during the last gedcom
+        import for two step adding.
+        """
+        # First we add the primary objects:
+        for key in list(self.import_cache.keys()):
+            obj = self.import_cache[key]
+            if isinstance(obj, Person):
+                self.dji.add_person(obj.serialize())
+            elif isinstance(obj, Family):
+                self.dji.add_family(obj.serialize())
+            elif isinstance(obj, Event):
+                self.dji.add_event(obj.serialize())
+            elif isinstance(obj, Place):
+                self.dji.add_place(obj.serialize())
+            elif isinstance(obj, Repository):
+                self.dji.add_repository(obj.serialize())
+            elif isinstance(obj, Citation):
+                self.dji.add_citation(obj.serialize())
+            elif isinstance(obj, Source):
+                self.dji.add_source(obj.serialize())
+            elif isinstance(obj, Note):
+                self.dji.add_note(obj.serialize())
+            elif isinstance(obj, MediaObject):
+                self.dji.add_media(obj.serialize())
+            elif isinstance(obj, Tag):
+                self.dji.add_tag(obj.serialize())
+        # Next we add the links:
+        for key in list(self.import_cache.keys()):
+            obj = self.import_cache[key]
+            if isinstance(obj, Person):
+                self.dji.add_person_detail(obj.serialize())
+            elif isinstance(obj, Family):
+                self.dji.add_family_detail(obj.serialize())
+            elif isinstance(obj, Event):
+                self.dji.add_event_detail(obj.serialize())
+            elif isinstance(obj, Place):
+                self.dji.add_place_detail(obj.serialize())
+            elif isinstance(obj, Repository):
+                self.dji.add_repository_detail(obj.serialize())
+            elif isinstance(obj, Citation):
+                self.dji.add_citation_detail(obj.serialize())
+            elif isinstance(obj, Source):
+                self.dji.add_source_detail(obj.serialize())
+            elif isinstance(obj, Note):
+                self.dji.add_note_detail(obj.serialize())
+            elif isinstance(obj, MediaObject):
+                self.dji.add_media_detail(obj.serialize())
+            elif isinstance(obj, Tag):
+                self.dji.add_tag_detail(obj.serialize())
+        self.use_import_cache = False
+        self.import_cache = {}
+        self.dji.update_publics()
+
+    def transaction_commit(self, txn):
+        pass
+
+    def request_rebuild(self):
+        # caches are ok, but let's compute public's
+        self.dji.update_publics()
+        self.emit('person-rebuild')
+        self.emit('family-rebuild')
+        self.emit('place-rebuild')
+        self.emit('source-rebuild')
+        self.emit('citation-rebuild')
+        self.emit('media-rebuild')
+        self.emit('event-rebuild')
+        self.emit('repository-rebuild')
+        self.emit('note-rebuild')
+        self.emit('tag-rebuild')
+
+    def get_undodb(self):
+        return None
+
+    def transaction_abort(self, txn):
+        pass
+
+    @staticmethod
+    def _validated_id_prefix(val, default):
+        if isinstance(val, str) and val:
+            try:
+                str_ = val % 1
+            except TypeError:           # missing conversion specifier
+                prefix_var = val + "%d"
+            except ValueError:          # incomplete format
+                prefix_var = default+"%04d"
+            else:
+                prefix_var = val        # OK as given
+        else:
+            prefix_var = default+"%04d" # not a string or empty string
+        return prefix_var
+
+    @staticmethod
+    def __id2user_format(id_pattern):
+        """
+        Return a method that accepts a Gramps ID and adjusts it to the users
+        format.
+        """
+        pattern_match = re.match(r"(.*)%[0 ](\d+)[diu]$", id_pattern)
+        if pattern_match:
+            str_prefix = pattern_match.group(1)
+            nr_width = pattern_match.group(2)
+            def closure_func(gramps_id):
+                if gramps_id and gramps_id.startswith(str_prefix):
+                    id_number = gramps_id[len(str_prefix):]
+                    if id_number.isdigit():
+                        id_value = int(id_number, 10)
+                        #if len(str(id_value)) > nr_width:
+                        #    # The ID to be imported is too large to fit in the
+                        #    # users format. For now just create a new ID,
+                        #    # because that is also what happens with IDs that
+                        #    # are identical to IDs already in the database. If
+                        #    # the problem of colliding import and already
+                        #    # present IDs is solved the code here also needs
+                        #    # some solution.
+                        #    gramps_id = id_pattern % 1
+                        #else:
+                        gramps_id = id_pattern % id_value
+                return gramps_id
+        else:
+            def closure_func(gramps_id):
+                return gramps_id
+        return closure_func
+
+    def set_person_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Person ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as I%d or I%04d.
+        """
+        self.person_prefix = self._validated_id_prefix(val, "I")
+        self.id2user_format = self.__id2user_format(self.person_prefix)
+
+    def set_citation_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Citation ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as C%d or C%04d.
+        """
+        self.citation_prefix = self._validated_id_prefix(val, "C")
+        self.cid2user_format = self.__id2user_format(self.citation_prefix)
+            
+    def set_source_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Source ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as S%d or S%04d.
+        """
+        self.source_prefix = self._validated_id_prefix(val, "S")
+        self.sid2user_format = self.__id2user_format(self.source_prefix)
+            
+    def set_object_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS MediaObject ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as O%d or O%04d.
+        """
+        self.mediaobject_prefix = self._validated_id_prefix(val, "O")
+        self.oid2user_format = self.__id2user_format(self.mediaobject_prefix)
+
+    def set_place_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Place ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as P%d or P%04d.
+        """
+        self.place_prefix = self._validated_id_prefix(val, "P")
+        self.pid2user_format = self.__id2user_format(self.place_prefix)
+
+    def set_family_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Family ID values. The string is
+        expected to be in the form of a simple text string, or in a format
+        that contains a C/Python style format string using %d, such as F%d
+        or F%04d.
+        """
+        self.family_prefix = self._validated_id_prefix(val, "F")
+        self.fid2user_format = self.__id2user_format(self.family_prefix)
+
+    def set_event_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Event ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as E%d or E%04d.
+        """
+        self.event_prefix = self._validated_id_prefix(val, "E")
+        self.eid2user_format = self.__id2user_format(self.event_prefix)
+
+    def set_repository_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Repository ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as R%d or R%04d.
+        """
+        self.repository_prefix = self._validated_id_prefix(val, "R")
+        self.rid2user_format = self.__id2user_format(self.repository_prefix)
+
+    def set_note_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Note ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as N%d or N%04d.
+        """
+        self.note_prefix = self._validated_id_prefix(val, "N")
+        self.nid2user_format = self.__id2user_format(self.note_prefix)
+
+    def __find_next_gramps_id(self, prefix, map_index, trans):
+        """
+        Helper function for find_next_<object>_gramps_id methods
+        """
+        index = prefix % map_index
+        while trans.get(str(index), txn=self.txn) is not None:
+            map_index += 1
+            index = prefix % map_index
+        map_index += 1
+        return (map_index, index)
+        
+    def find_next_person_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Person object based off the 
+        person ID prefix.
+        """
+        self.pmap_index, gid = self.__find_next_gramps_id(self.person_prefix,
+                                          self.pmap_index, self.id_trans)
+        return gid
+
+    def find_next_place_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Place object based off the 
+        place ID prefix.
+        """
+        self.lmap_index, gid = self.__find_next_gramps_id(self.place_prefix,
+                                          self.lmap_index, self.pid_trans)
+        return gid
+
+    def find_next_event_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Event object based off the 
+        event ID prefix.
+        """
+        self.emap_index, gid = self.__find_next_gramps_id(self.event_prefix,
+                                          self.emap_index, self.eid_trans)
+        return gid
+
+    def find_next_object_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a MediaObject object based
+        off the media object ID prefix.
+        """
+        self.omap_index, gid = self.__find_next_gramps_id(self.mediaobject_prefix,
+                                          self.omap_index, self.oid_trans)
+        return gid
+
+    def find_next_citation_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Citation object based off the 
+        citation ID prefix.
+        """
+        self.cmap_index, gid = self.__find_next_gramps_id(self.citation_prefix,
+                                          self.cmap_index, self.cid_trans)
+        return gid
+
+    def find_next_source_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Source object based off the 
+        source ID prefix.
+        """
+        self.smap_index, gid = self.__find_next_gramps_id(self.source_prefix,
+                                          self.smap_index, self.sid_trans)
+        return gid
+
+    def find_next_family_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Family object based off the 
+        family ID prefix.
+        """
+        self.fmap_index, gid = self.__find_next_gramps_id(self.family_prefix,
+                                          self.fmap_index, self.fid_trans)
+        return gid
+
+    def find_next_repository_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Respository object based 
+        off the repository ID prefix.
+        """
+        self.rmap_index, gid = self.__find_next_gramps_id(self.repository_prefix,
+                                          self.rmap_index, self.rid_trans)
+        return gid
+
+    def find_next_note_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Note object based off the 
+        note ID prefix.
+        """
+        self.nmap_index, gid = self.__find_next_gramps_id(self.note_prefix,
+                                          self.nmap_index, self.nid_trans)
+        return gid
+
+    def get_mediapath(self):
+        return None
+
+    def get_name_group_keys(self):
+        return []
+
+    def get_name_group_mapping(self, key):
+        return None
+
+    def get_researcher(self):
+        obj = Researcher()
+        return obj
+
+    def get_tag_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Tag.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Tag.all()]
+
+    def get_person_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Person.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Person.all()]
+
+    def get_family_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Family.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Family.all()]
+
+    def get_event_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Event.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Event.all()]
+
+    def get_citation_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Citation.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Citation.all()]
+
+    def get_source_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Source.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Source.all()]
+
+    def get_place_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Place.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Place.all()]
+
+    def get_repository_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Repository.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Repository.all()]
+
+    def get_media_object_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Media.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Media.all()]
+
+    def get_note_handles(self, sort_handles=False):
+        if sort_handles:
+            return [item.handle for item in self.dji.Note.all().order_by("handle")]
+        else:
+            return [item.handle for item in self.dji.Note.all()]
+
+    def get_media_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            media = self.dji.Media.get(handle=handle)
+        except:
+            return None
+        return self.make_media(media)
+
+    def get_event_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            event = self.dji.Event.get(handle=handle)
+        except:
+            return None
+        return self.make_event(event)
+
+    def get_family_from_handle(self, handle): 
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            family = self.dji.Family.get(handle=handle)
+        except:
+            return None
+        return self.make_family(family)
+
+    def get_repository_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            repository = self.dji.Repository.get(handle=handle)
+        except:
+            return None
+        return self.make_repository(repository)
+
+    def get_person_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            person = self.dji.Person.get(handle=handle)
+        except:
+            return None
+        return self.make_person(person)
+
+    def get_tag_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            tag = self.dji.Tag.get(handle=handle)
+        except:
+            return None
+        return self.make_tag(tag)
+
+    def make_repository(self, repository):
+        if self.use_db_cache and repository.cache:
+            data = repository.from_cache()
+        else:
+            data = self.dji.get_repository(repository)
+        return Repository.create(data)
+
+    def make_citation(self, citation):
+        if self.use_db_cache and citation.cache:
+            data = citation.from_cache()
+        else:
+            data = self.dji.get_citation(citation)
+        return Citation.create(data)
+
+    def make_source(self, source):
+        if self.use_db_cache and source.cache:
+            data = source.from_cache()
+        else:
+            data = self.dji.get_source(source)
+        return Source.create(data)
+
+    def make_family(self, family):
+        if self.use_db_cache and family.cache:
+            data = family.from_cache()
+        else:
+            data = self.dji.get_family(family)
+        return Family.create(data)
+
+    def make_person(self, person):
+        if self.use_db_cache and person.cache:
+            data = person.from_cache()
+        else:
+            data = self.dji.get_person(person)
+        return Person.create(data)
+
+    def make_event(self, event):
+        if self.use_db_cache and event.cache:
+            data = event.from_cache()
+        else:
+            data = self.dji.get_event(event)
+        return Event.create(data)
+
+    def make_note(self, note):
+        if self.use_db_cache and note.cache:
+            data = note.from_cache()
+        else:
+            data = self.dji.get_note(note)
+        return Note.create(data)
+
+    def make_tag(self, tag):
+        data = self.dji.get_tag(tag)
+        return Tag.create(data)
+
+    def make_place(self, place):
+        if self.use_db_cache and place.cache:
+            data = place.from_cache()
+        else:
+            data = self.dji.get_place(place)
+        return Place.create(data)
+
+    def make_media(self, media):
+        if self.use_db_cache and media.cache:
+            data = media.from_cache()
+        else:
+            data = self.dji.get_media(media)
+        return MediaObject.create(data)
+
+    def get_place_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            place = self.dji.Place.get(handle=handle)
+        except:
+            return None
+        return self.make_place(place)
+
+    def get_citation_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            citation = self.dji.Citation.get(handle=handle)
+        except:
+            return None
+        return self.make_citation(citation)
+
+    def get_source_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            source = self.dji.Source.get(handle=handle)
+        except:
+            return None
+        return self.make_source(source)
+
+    def get_note_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            note = self.dji.Note.get(handle=handle)
+        except:
+            return None
+        return self.make_note(note)
+
+    def get_object_from_handle(self, handle):
+        if handle in self.import_cache:
+            return self.import_cache[handle]
+        try:
+            media = self.dji.Media.get(handle=handle)
+        except:
+            return None
+        return self.make_media(media)
+
+    def get_default_person(self):
+        people = self.dji.Person.all()
+        if people.count() > 0:
+            return self.make_person(people[0])
+        return None
+
+    def iter_people(self):
+        return (self.get_person_from_handle(person.handle) 
+                for person in self.dji.Person.all())
+
+    def iter_person_handles(self):
+        return (person.handle for person in self.dji.Person.all())
+
+    def iter_families(self):
+        return (self.get_family_from_handle(family.handle) 
+                for family in self.dji.Family.all())
+
+    def iter_family_handles(self):
+        return (family.handle for family in self.dji.Family.all())
+
+    def iter_notes(self):
+        return (self.get_note_from_handle(note.handle) 
+                for note in self.dji.Note.all())
+
+    def iter_note_handles(self):
+        return (note.handle for note in self.dji.Note.all())
+
+    def iter_events(self):
+        return (self.get_event_from_handle(event.handle) 
+                for event in self.dji.Event.all())
+
+    def iter_event_handles(self):
+        return (event.handle for event in self.dji.Event.all())
+
+    def iter_places(self):
+        return (self.get_place_from_handle(place.handle) 
+                for place in self.dji.Place.all())
+
+    def iter_place_handles(self):
+        return (place.handle for place in self.dji.Place.all())
+
+    def iter_repositories(self):
+        return (self.get_repository_from_handle(repository.handle) 
+                for repository in self.dji.Repository.all())
+
+    def iter_repository_handles(self):
+        return (repository.handle for repository in self.dji.Repository.all())
+
+    def iter_sources(self):
+        return (self.get_source_from_handle(source.handle) 
+                for source in self.dji.Source.all())
+
+    def iter_source_handles(self):
+        return (source.handle for source in self.dji.Source.all())
+
+    def iter_citations(self):
+        return (self.get_citation_from_handle(citation.handle) 
+                for citation in self.dji.Citation.all())
+
+    def iter_citation_handles(self):
+        return (citation.handle for citation in self.dji.Citation.all())
+
+    def iter_tags(self):
+        return (self.get_tag_from_handle(tag.handle) 
+                for tag in self.dji.Tag.all())
+
+    def iter_tag_handles(self):
+        return (tag.handle for tag in self.dji.Tag.all())
+
+    def iter_media_objects(self):
+        return (self.get_media_from_handle(media.handle) 
+                for media in self.dji.Media.all())
+
+    def get_tag_from_name(self, name):
+        try:
+            tag = self.dji.Tag.filter(name=name)
+            return self.make_tag(tag[0])
+        except:
+            return None
+
+    def get_person_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Person.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_person(match_list[0])
+        else:
+            return None
+
+    def get_family_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        try:
+            family = self.dji.Family.get(gramps_id=gramps_id)
+        except:
+            return None
+        return self.make_family(family)
+
+    def get_source_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Source.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_source(match_list[0])
+        else:
+            return None
+
+    def get_citation_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Citation.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_citation(match_list[0])
+        else:
+            return None
+
+    def get_event_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Event.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_event(match_list[0])
+        else:
+            return None
+
+    def get_object_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Media.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_media(match_list[0])
+        else:
+            return None
+
+    def get_place_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Place.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_place(match_list[0])
+        else:
+            return None
+
+    def get_repository_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Repsoitory.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_repository(match_list[0])
+        else:
+            return None
+
+    def get_note_from_gramps_id(self, gramps_id):
+        if self.import_cache:
+            for handle in self.import_cache:
+                if self.import_cache[handle].gramps_id == gramps_id:
+                    return self.import_cache[handle]
+        match_list = self.dji.Note.filter(gramps_id=gramps_id)
+        if match_list.count() > 0:
+            return self.make_note(match_list[0])
+        else:
+            return None
+
+    def get_number_of_people(self):
+        return self.dji.Person.count()
+
+    def get_number_of_events(self):
+        return self.dji.Event.count()
+
+    def get_number_of_places(self):
+        return self.dji.Place.count()
+
+    def get_number_of_tags(self):
+        return self.dji.Tag.count()
+
+    def get_number_of_families(self):
+        return self.dji.Family.count()
+
+    def get_number_of_notes(self):
+        return self.dji.Note.count()
+
+    def get_number_of_citations(self):
+        return self.dji.Citation.count()
+
+    def get_number_of_sources(self):
+        return self.dji.Source.count()
+
+    def get_number_of_media_objects(self):
+        return self.dji.Media.count()
+
+    def get_number_of_repositories(self):
+        return self.dji.Repository.count()
+
+    def get_place_cursor(self):
+        return Cursor(self.dji.Place, self.get_raw_place_data)
+
+    def get_person_cursor(self):
+        return Cursor(self.dji.Person, self.get_raw_person_data)
+
+    def get_family_cursor(self):
+        return Cursor(self.dji.Family, self.get_raw_family_data)
+
+    def get_event_cursor(self):
+        return Cursor(self.dji.Event, self.get_raw_event_data)
+
+    def get_citation_cursor(self):
+        return Cursor(self.dji.Citation, self.get_raw_citation_data)
+
+    def get_source_cursor(self):
+        return Cursor(self.dji.Source, self.get_raw_source_data)
+
+    def get_note_cursor(self):
+        return Cursor(self.dji.Note, self.get_raw_note_data)
+
+    def get_tag_cursor(self):
+        return Cursor(self.dji.Tag, self.get_raw_tag_data)
+
+    def get_repository_cursor(self):
+        return Cursor(self.dji.Repository, self.get_raw_repository_data)
+
+    def get_media_cursor(self):
+        return Cursor(self.dji.Media, self.get_raw_object_data)
+
+    def has_gramps_id(self, obj_key, gramps_id):
+        key2table = {
+            PERSON_KEY:     self.dji.Person, 
+            FAMILY_KEY:     self.dji.Family, 
+            SOURCE_KEY:     self.dji.Source, 
+            CITATION_KEY:   self.dji.Citation, 
+            EVENT_KEY:      self.dji.Event, 
+            MEDIA_KEY:      self.dji.Media, 
+            PLACE_KEY:      self.dji.Place, 
+            REPOSITORY_KEY: self.dji.Repository, 
+            NOTE_KEY:       self.dji.Note, 
+            }
+        table = key2table[obj_key]
+        return table.filter(gramps_id=gramps_id).count() > 0
+
+    def has_person_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Person.filter(handle=handle).count() == 1
+
+    def has_family_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Family.filter(handle=handle).count() == 1
+
+    def has_citation_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Citation.filter(handle=handle).count() == 1
+
+    def has_source_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Source.filter(handle=handle).count() == 1
+
+    def has_repository_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Repository.filter(handle=handle).count() == 1
+
+    def has_note_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Note.filter(handle=handle).count() == 1
+
+    def has_place_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Place.filter(handle=handle).count() == 1
+
+    def has_event_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Event.filter(handle=handle).count() == 1
+
+    def has_tag_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Tag.filter(handle=handle).count() == 1
+
+    def has_object_handle(self, handle):
+        if handle in self.import_cache:
+            return True
+        return self.dji.Media.filter(handle=handle).count() == 1
+
+    def has_name_group_key(self, key):
+        # FIXME:
+        return False
+
+    def set_name_group_mapping(self, key, value):
+        # FIXME:
+        pass
+
+    def set_default_person_handle(self, handle):
+        pass
+
+    def set_mediapath(self, mediapath):
+        pass
+
+    def get_raw_person_data(self, handle):
+        try:
+            return self.dji.get_person(self.dji.Person.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_family_data(self, handle):
+        try:
+            return self.dji.get_family(self.dji.Family.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_citation_data(self, handle):
+        try:
+            return self.dji.get_citation(self.dji.Citation.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_source_data(self, handle):
+        try:
+            return self.dji.get_source(self.dji.Source.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_repository_data(self, handle):
+        try:
+            return self.dji.get_repository(self.dji.Repository.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_note_data(self, handle):
+        try:
+            return self.dji.get_note(self.dji.Note.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_place_data(self, handle):
+        try:
+            return self.dji.get_place(self.dji.Place.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_object_data(self, handle):
+        try:
+            return self.dji.get_media(self.dji.Media.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_tag_data(self, handle):
+        try:
+            return self.dji.get_tag(self.dji.Tag.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def get_raw_event_data(self, handle):
+        try:
+            return self.dji.get_event(self.dji.Event.get(handle=handle))
+        except:
+            if handle in self.import_cache:
+                return self.import_cache[handle].serialize()
+            else:
+                return None
+
+    def add_person(self, person, trans, set_gid=True):
+        if not person.handle:
+            person.handle = create_id()
+        if not person.gramps_id and set_gid:
+            person.gramps_id = self.find_next_person_gramps_id()
+        self.commit_person(person, trans)
+        self.emit("person-add", ([person.handle],))
+        return person.handle
+
+    def add_family(self, family, trans, set_gid=True):
+        if not family.handle:
+            family.handle = create_id()
+        if not family.gramps_id and set_gid:
+            family.gramps_id = self.find_next_family_gramps_id()
+        self.commit_family(family, trans)
+        self.emit("family-add", ([family.handle],))
+        return family.handle
+
+    def add_citation(self, citation, trans, set_gid=True):
+        if not citation.handle:
+            citation.handle = create_id()
+        if not citation.gramps_id and set_gid:
+            citation.gramps_id = self.find_next_citation_gramps_id()
+        self.commit_citation(citation, trans)
+        self.emit("citation-add", ([citation.handle],))
+        return citation.handle
+
+    def add_source(self, source, trans, set_gid=True):
+        if not source.handle:
+            source.handle = create_id()
+        if not source.gramps_id and set_gid:
+            source.gramps_id = self.find_next_source_gramps_id()
+        self.commit_source(source, trans)
+        self.emit("source-add", ([source.handle],))
+        return source.handle
+
+    def add_repository(self, repository, trans, set_gid=True):
+        if not repository.handle:
+            repository.handle = create_id()
+        if not repository.gramps_id and set_gid:
+            repository.gramps_id = self.find_next_repository_gramps_id()
+        self.commit_repository(repository, trans)
+        self.emit("repository-add", ([repository.handle],))
+        return repository.handle
+
+    def add_note(self, note, trans, set_gid=True):
+        if not note.handle:
+            note.handle = create_id()
+        if not note.gramps_id and set_gid:
+            note.gramps_id = self.find_next_note_gramps_id()
+        self.commit_note(note, trans)
+        self.emit("note-add", ([note.handle],))
+        return note.handle
+
+    def add_place(self, place, trans, set_gid=True):
+        if not place.handle:
+            place.handle = create_id()
+        if not place.gramps_id and set_gid:
+            place.gramps_id = self.find_next_place_gramps_id()
+        self.commit_place(place, trans)
+        return place.handle
+
+    def add_event(self, event, trans, set_gid=True):
+        if not event.handle:
+            event.handle = create_id()
+        if not event.gramps_id and set_gid:
+            event.gramps_id = self.find_next_event_gramps_id()
+        self.commit_event(event, trans)
+        return event.handle
+
+    def add_tag(self, tag, trans):
+        if not tag.handle:
+            tag.handle = create_id()
+        self.commit_event(tag, trans)
+        return tag.handle
+
+    def add_object(self, obj, transaction, set_gid=True):
+        """
+        Add a MediaObject to the database, assigning internal IDs if they have
+        not already been defined.
+        
+        If not set_gid, then gramps_id is not set.
+        """
+        if not obj.handle:
+            obj.handle = create_id()
+        if not obj.gramps_id and set_gid:
+            obj.gramps_id = self.find_next_object_gramps_id()
+        self.commit_media_object(obj, transaction)
+        return obj.handle
+
+    def commit_person(self, person, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[person.handle] = person
+        else:
+            raw = person.serialize()
+            items = self.dji.Person.filter(handle=person.handle)
+            if items.count() > 0:
+                # Hack, for the moment: delete and re-add
+                items[0].delete()
+            self.dji.add_person(person.serialize())
+            self.dji.add_person_detail(person.serialize())
+            if items.count() > 0:
+                self.emit("person-update", ([person.handle],))
+            else:
+                self.emit("person-add", ([person.handle],))
+
+    def commit_family(self, family, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[family.handle] = family
+        else:
+            raw = family.serialize()
+            items = self.dji.Family.filter(handle=family.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_family(family.serialize())
+            self.dji.add_family_detail(family.serialize())
+            if items.count() > 0:
+                self.emit("family-update", ([family.handle],))
+            else:
+                self.emit("family-add", ([family.handle],))
+
+    def commit_citation(self, citation, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[citation.handle] = citation
+        else:
+            raw = citation.serialize()
+            items = self.dji.Citation.filter(handle=citation.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_citation(citation.serialize())
+            self.dji.add_citation_detail(citation.serialize())
+            if items.count() > 0:
+                self.emit("citation-update", ([citation.handle],))
+            else:
+                self.emit("citation-add", ([citation.handle],))
+
+    def commit_source(self, source, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[source.handle] = source
+        else:
+            raw = source.serialize()
+            items = self.dji.Source.filter(handle=source.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_source(source.serialize())
+            self.dji.add_source_detail(source.serialize())
+            if items.count() > 0:
+                self.emit("source-update", ([source.handle],))
+            else:
+                self.emit("source-add", ([source.handle],))
+
+    def commit_repository(self, repository, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[repository.handle] = repository
+        else:
+            raw = repository.serialize()
+            items = self.dji.Repository.filter(handle=repository.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_repository(repository.serialize())
+            self.dji.add_repository_detail(repository.serialize())
+            if items.count() > 0:
+                self.emit("repository-update", ([repository.handle],))
+            else:
+                self.emit("repository-add", ([repository.handle],))
+
+    def commit_note(self, note, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[note.handle] = note
+        else:
+            raw = note.serialize()
+            items = self.dji.Note.filter(handle=note.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_note(note.serialize())
+            self.dji.add_note_detail(note.serialize())
+            if items.count() > 0:
+                self.emit("note-update", ([note.handle],))
+            else:
+                self.emit("note-add", ([note.handle],))
+
+    def commit_place(self, place, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[place.handle] = place
+        else:
+            raw = place.serialize()
+            items = self.dji.Place.filter(handle=place.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_place(place.serialize())
+            self.dji.add_place_detail(place.serialize())
+            if items.count() > 0:
+                self.emit("place-update", ([place.handle],))
+            else:
+                self.emit("place-add", ([place.handle],))
+
+    def commit_event(self, event, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[event.handle] = event
+        else:
+            raw = event.serialize()
+            items = self.dji.Event.filter(handle=event.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_event(event.serialize())
+            self.dji.add_event_detail(event.serialize())
+            if items.count() > 0:
+                self.emit("event-update", ([event.handle],))
+            else:
+                self.emit("event-add", ([event.handle],))
+
+    def commit_tag(self, tag, trans, change_time=None):
+        if self.use_import_cache:
+            self.import_cache[tag.handle] = tag
+        else:
+            raw = tag.serialize()
+            items = self.dji.Tag.filter(handle=tag.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_tag(tag.serialize())
+            self.dji.add_tag_detail(tag.serialize())
+            if items.count() > 0:
+                self.emit("tag-update", ([tag.handle],))
+            else:
+                self.emit("tag-add", ([tag.handle],))
+
+    def commit_media_object(self, media, transaction, change_time=None):
+        """
+        Commit the specified MediaObject to the database, storing the changes
+        as part of the transaction.
+        """
+        if self.use_import_cache:
+            self.import_cache[obj.handle] = media
+        else:
+            raw = media.serialize()
+            items = self.dji.Media.filter(handle=media.handle)
+            if items.count() > 0:
+                items[0].delete()
+            self.dji.add_media(media.serialize())
+            self.dji.add_media_detail(media.serialize())
+            if items.count() > 0:
+                self.emit("media-update", ([media.handle],))
+            else:
+                self.emit("media-add", ([media.handle],))
+
+    def get_gramps_ids(self, obj_key):
+        key2table = {
+            PERSON_KEY:     self.id_trans, 
+            FAMILY_KEY:     self.fid_trans, 
+            CITATION_KEY:   self.cid_trans, 
+            SOURCE_KEY:     self.sid_trans, 
+            EVENT_KEY:      self.eid_trans, 
+            MEDIA_KEY:      self.oid_trans, 
+            PLACE_KEY:      self.pid_trans, 
+            REPOSITORY_KEY: self.rid_trans, 
+            NOTE_KEY:       self.nid_trans, 
+            }
+
+        table = key2table[obj_key]
+        return list(table.keys())
+
+    def transaction_begin(self, transaction):
+        return 
+
+    def set_researcher(self, owner):
+        pass
+
+    def copy_from_db(self, db):
+        """
+        A (possibily) implementation-specific method to get data from
+        db into this database.
+        """
+        # First we add the primary objects:
+        for key in db._tables.keys():
+            cursor = db._tables[key]["cursor_func"]
+            for (handle, data) in cursor():
+                if key == "Person":
+                    self.dji.add_person(data)
+                elif key == "Family":
+                    self.dji.add_family(data)
+                elif key == "Event":
+                    self.dji.add_event(data)
+                elif key == "Place":
+                    self.dji.add_place(data)
+                elif key == "Repository":
+                    self.dji.add_repository(data)
+                elif key == "Citation":
+                    self.dji.add_citation(data)
+                elif key == "Source":
+                    self.dji.add_source(data)
+                elif key == "Note":
+                    self.dji.add_note(data)
+                elif key == "Media":
+                    self.dji.add_media(data)
+                elif key == "Tag":
+                    self.dji.add_tag(data)
+        for key in db._tables.keys():
+            cursor = db._tables[key]["cursor_func"]
+            for (handle, data) in cursor():
+                if key == "Person":
+                    self.dji.add_person_detail(data)
+                elif key == "Family":
+                    self.dji.add_family_detail(data)
+                elif key == "Event":
+                    self.dji.add_event_detail(data)
+                elif key == "Place":
+                    self.dji.add_place_detail(data)
+                elif key == "Repository":
+                    self.dji.add_repository_detail(data)
+                elif key == "Citation":
+                    self.dji.add_citation_detail(data)
+                elif key == "Source":
+                    self.dji.add_source_detail(data)
+                elif key == "Note":
+                    self.dji.add_note_detail(data)
+                elif key == "Media":
+                    self.dji.add_media_detail(data)
+                elif key == "Tag":
+                    self.dji.add_tag_detail(data)
+            # Next we add the links:
+        self.dji.update_publics()
+
+    def get_from_name_and_handle(self, table_name, handle):
+        """
+        Returns a gen.lib object (or None) given table_name and
+        handle.
+
+        Examples:
+
+        >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008")
+        >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23")
+        """
+        if table_name in self._tables:
+            return self._tables[table_name]["handle_func"](handle)
+        return None
+
+    def is_empty(self):
+        """
+        Is the database empty?
+        """
+        return (self.get_number_of_people() == 0 and
+                self.get_number_of_events() == 0 and
+                self.get_number_of_places() == 0 and
+                self.get_number_of_tags() == 0 and
+                self.get_number_of_families() == 0 and
+                self.get_number_of_notes() == 0 and
+                self.get_number_of_citations() == 0 and
+                self.get_number_of_sources() == 0 and
+                self.get_number_of_media_objects() == 0 and
+                self.get_number_of_repositories() == 0)
+                
+    __callback_map = {}
+
+    def set_prefixes(self, person, media, family, source, citation,
+                     place, event, repository, note):
+        self.set_person_id_prefix(person)
+        self.set_object_id_prefix(media)
+        self.set_family_id_prefix(family)
+        self.set_source_id_prefix(source)
+        self.set_citation_id_prefix(citation)
+        self.set_place_id_prefix(place)
+        self.set_event_id_prefix(event)
+        self.set_repository_id_prefix(repository)
+        self.set_note_id_prefix(note)
+
+    def has_changed(self):
+        return False
+
+    def find_backlink_handles(self, handle, include_classes=None):
+        ## FIXME: figure out how to get objects that refer
+        ## to this handle
+        return []
+
+    def get_note_bookmarks(self):
+        return self.note_bookmarks
+
+    def get_media_bookmarks(self):
+        return self.media_bookmarks
+
+    def get_repo_bookmarks(self):
+        return self.repo_bookmarks
+
+    def get_citation_bookmarks(self):
+        return self.citation_bookmarks
+
+    def get_source_bookmarks(self):
+        return self.source_bookmarks
+
+    def get_place_bookmarks(self):
+        return self.place_bookmarks
+
+    def get_event_bookmarks(self):
+        return self.event_bookmarks
+
+    def get_bookmarks(self):
+        return self.bookmarks
+
+    def get_family_bookmarks(self):
+        return self.family_bookmarks
+
+    def get_save_path(self):
+        return "/tmp/"
+
+    ## Get types:
+    def get_event_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Event instances
+        in the database.
+        """
+        return list(self.event_attributes)
+
+    def get_event_types(self):
+        """
+        Return a list of all event types in the database.
+        """
+        return list(self.event_names)
+
+    def get_person_event_types(self):
+        """
+        Deprecated:  Use get_event_types
+        """
+        return list(self.event_names)
+
+    def get_person_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Person instances 
+        in the database.
+        """
+        return list(self.individual_attributes)
+
+    def get_family_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Family instances 
+        in the database.
+        """
+        return list(self.family_attributes)
+
+    def get_family_event_types(self):
+        """
+        Deprecated:  Use get_event_types
+        """
+        return list(self.event_names)
+
+    def get_media_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Media and MediaRef 
+        instances in the database.
+        """
+        return list(self.media_attributes)
+
+    def get_family_relation_types(self):
+        """
+        Return a list of all relationship types assocated with Family
+        instances in the database.
+        """
+        return list(self.family_rel_types)
+
+    def get_child_reference_types(self):
+        """
+        Return a list of all child reference types assocated with Family
+        instances in the database.
+        """
+        return list(self.child_ref_types)
+
+    def get_event_roles(self):
+        """
+        Return a list of all custom event role names assocated with Event
+        instances in the database.
+        """
+        return list(self.event_role_names)
+
+    def get_name_types(self):
+        """
+        Return a list of all custom names types assocated with Person
+        instances in the database.
+        """
+        return list(self.name_types)
+
+    def get_origin_types(self):
+        """
+        Return a list of all custom origin types assocated with Person/Surname
+        instances in the database.
+        """
+        return list(self.origin_types)
+
+    def get_repository_types(self):
+        """
+        Return a list of all custom repository types assocated with Repository 
+        instances in the database.
+        """
+        return list(self.repository_types)
+
+    def get_note_types(self):
+        """
+        Return a list of all custom note types assocated with Note instances 
+        in the database.
+        """
+        return list(self.note_types)
+
+    def get_source_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Source/Citation
+        instances in the database.
+        """
+        return list(self.source_attributes)
+
+    def get_source_media_types(self):
+        """
+        Return a list of all custom source media types assocated with Source 
+        instances in the database.
+        """
+        return list(self.source_media_types)
+
+    def get_url_types(self):
+        """
+        Return a list of all custom names types assocated with Url instances 
+        in the database.
+        """
+        return list(self.url_types)
+
+    def get_place_types(self):
+        """
+        Return a list of all custom place types assocated with Place instances
+        in the database.
+        """
+        return list(self.place_types)
+
+    def get_default_handle(self):
+        people = self.dji.Person.all()
+        if people.count() > 0:
+            return people[0].handle
+        return None
+
+    def close(self):
+        pass
+
+    def get_surname_list(self):
+        return []
+
+    def is_open(self):
+        return True
+
+    def get_table_names(self):
+        """Return a list of valid table names."""
+        return list(self._tables.keys())
+
+    def find_initial_person(self):
+        return self.get_default_person()
+
+    # Removals:
+    def remove_person(self, handle, txn):
+        self.dji.Person.filter(handle=handle)[0].delete()
+        self.emit("person-delete", ([handle],))
+
+    def remove_source(self, handle, transaction):
+        self.dji.Source.filter(handle=handle)[0].delete()
+        self.emit("source-delete", ([handle],))
+
+    def remove_citation(self, handle, transaction):
+        self.dji.Citation.filter(handle=handle)[0].delete()
+        self.emit("citation-delete", ([handle],))
+
+    def remove_event(self, handle, transaction):
+        self.dji.Event.filter(handle=handle)[0].delete()
+        self.emit("event-delete", ([handle],))
+
+    def remove_object(self, handle, transaction):
+        self.dji.Media.filter(handle=handle)[0].delete()
+        self.emit("media-delete", ([handle],))
+
+    def remove_place(self, handle, transaction):
+        self.dji.Place.filter(handle=handle)[0].delete()
+        self.emit("place-delete", ([handle],))
+
+    def remove_family(self, handle, transaction):
+        self.dji.Family.filter(handle=handle)[0].delete()
+        self.emit("family-delete", ([handle],))
+
+    def remove_repository(self, handle, transaction):
+        self.dji.Repository.filter(handle=handle)[0].delete()
+        self.emit("repository-delete", ([handle],))
+
+    def remove_note(self, handle, transaction):
+        self.dji.Note.filter(handle=handle)[0].delete()
+        self.emit("note-delete", ([handle],))
+
+    def remove_tag(self, handle, transaction):
+        self.dji.Tag.filter(handle=handle)[0].delete()
+        self.emit("tag-delete", ([handle],))
+
+    def remove_from_surname_list(self, person):
+        ## FIXME
+        pass
+    
+    def get_dbname(self):
+        return "Django Database"
+
+    ## missing
+
+    def find_place_child_handles(self, handle):
+        pass
+
+    def get_cursor(self, table, txn=None, update=False, commit=False):
+        pass
+
+    def get_from_name_and_handle(self, table_name, handle):
+        """
+        Returns a gen.lib object (or None) given table_name and
+        handle.
+
+        Examples:
+
+        >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008")
+        >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23")
+        """
+        if table_name in self._tables:
+            return self._tables[table_name]["handle_func"](handle)
+        return None
+
+    def get_number_of_records(self, table):
+        pass
+
+    def get_place_parent_cursor(self):
+        pass
+
+    def get_place_tree_cursor(self):
+        pass
+
+    def get_table_metadata(self, table_name):
+        """Return the metadata for a valid table name."""
+        if table_name in self._tables:
+            return self._tables[table_name]
+        return None
+
+    def get_transaction_class(self):
+        pass
+
+    def undo(self, update_history=True):
+        # FIXME:
+        return self.undodb.undo(update_history)
+
+    def redo(self, update_history=True):
+        # FIXME:
+        return self.undodb.redo(update_history)
+
+    def backup(self):
+        pass
+
+    def restore(self):
+        pass

From beb8b8e3ab447cc8f28e06eccbd5b1aa8e3fa624 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Wed, 13 May 2015 12:36:17 -0400
Subject: [PATCH 056/105] Loads tree based on id in database.txt

---
 gramps/cli/grampscli.py             | 10 +++++++++-
 gramps/gui/dbloader.py              | 10 +++++++++-
 gramps/plugins/database/djangodb.py | 19 ++++++++++++++++---
 3 files changed, 34 insertions(+), 5 deletions(-)

diff --git a/gramps/cli/grampscli.py b/gramps/cli/grampscli.py
index d1d3cfc07..d7b9afea6 100644
--- a/gramps/cli/grampscli.py
+++ b/gramps/cli/grampscli.py
@@ -47,6 +47,7 @@ LOG = logging.getLogger(".grampscli")
 from gramps.gen.display.name import displayer as name_displayer
 from gramps.gen.config import config
 from gramps.gen.const import PLUGINS_DIR, USER_PLUGINS
+from gramps.gen.db.dbconst import DBBACKEND
 from gramps.gen.errors import DbError
 from gramps.gen.dbstate import DbState
 from gramps.gen.db.exceptions import (DbUpgradeRequiredError, 
@@ -151,7 +152,14 @@ class CLIDbLoader(object):
         else:
             mode = 'w'
 
-        db = self.dbstate.make_database("bsddb")
+        dbid_path = os.path.join(filename, DBBACKEND)
+        if os.path.isfile(dbid_path):
+            with open(dbid_path) as fp:
+                dbid = fp.read().strip()
+        else:
+            dbid = "bsddb"
+
+        db = self.dbstate.make_database(dbid)
         
         self.dbstate.change_database(db)
         self.dbstate.db.disable_signals()
diff --git a/gramps/gui/dbloader.py b/gramps/gui/dbloader.py
index f3d303b15..d95c6bb9a 100644
--- a/gramps/gui/dbloader.py
+++ b/gramps/gui/dbloader.py
@@ -52,6 +52,7 @@ from gi.repository import GObject
 #
 #-------------------------------------------------------------------------
 from gramps.gen.const import GRAMPS_LOCALE as glocale
+from gramps.gen.db.dbconst import DBBACKEND
 _ = glocale.translation.gettext
 from gramps.cli.grampscli import CLIDbLoader
 from gramps.gen.config import config
@@ -304,7 +305,14 @@ class DbLoader(CLIDbLoader):
         else:
             mode = 'w'
 
-        db = self.dbstate.make_database("bsddb")
+        dbid_path = os.path.join(filename, DBBACKEND)
+        if os.path.isfile(dbid_path):
+            with open(dbid_path) as fp:
+                dbid = fp.read().strip()
+        else:
+            dbid = "bsddb"
+
+        db = self.dbstate.make_database(dbid)
         db.disable_signals()
         self.dbstate.no_database()
 
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index adb28dc54..e0d8c043a 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -55,6 +55,11 @@ from gramps.gen.db import (PERSON_KEY,
 from gramps.gen.utils.id import create_id
 from django.db import transaction
 
+## add this directory to sys path, so we can find django_support later:
+import sys
+import os
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+
 class Environment(object):
     """
     Implements the Environment API.
@@ -322,10 +327,15 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if directory:
             self.load(directory)
 
-    def load(self, directory, pulse_progress=None, mode=None):
+    def load(self, directory, pulse_progress=None, mode=None, 
+             force_schema_upgrade=False,
+             force_bsddb_upgrade=False,
+             force_bsddb_downgrade=False,
+             force_python_upgrade=False):
         self._directory = directory
         from django.conf import settings
-        default_settings = {}
+        default_settings = {"__file__": 
+                            os.path.join(directory, "default_settings.py")}
         settings_file = os.path.join(directory, "default_settings.py")
         with open(settings_file) as f:
             code = compile(f.read(), settings_file, 'exec')
@@ -1785,7 +1795,10 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return self.family_bookmarks
 
     def get_save_path(self):
-        return "/tmp/"
+        return self._directory
+
+    def set_save_path(self, directory):
+        self._directory = directory
 
     ## Get types:
     def get_event_attribute_types(self):

From 8a42966c1f209596ae3bdbb59abbe78229a38202 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Wed, 13 May 2015 19:26:14 -0400
Subject: [PATCH 057/105] DictionaryDb: adding missing functions, bringing up
 to date

---
 gramps/plugins/database/dictionarydb.py | 454 ++++++++++++++++++++----
 gramps/plugins/database/djangodb.py     |   6 +-
 gramps/webapp/utils.py                  |   6 +-
 3 files changed, 395 insertions(+), 71 deletions(-)

diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index a3dbd9586..621425e2d 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -29,6 +29,8 @@ import base64
 import time
 import re
 from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn
+from gramps.gen.utils.callback import Callback
+from gramps.gen.updatecallback import UpdateCallback
 from gramps.gen.db import (PERSON_KEY,
                            FAMILY_KEY,
                            CITATION_KEY,
@@ -53,33 +55,101 @@ from gramps.gen.lib.repo import Repository
 from gramps.gen.lib.note import Note
 from gramps.gen.lib.tag import Tag
 
+class Environment(object):
+    """
+    Implements the Environment API.
+    """
+    def __init__(self, db):
+        self.db = db
+
+    def txn_begin(self):
+        return DictionaryTxn("DictionaryDb Transaction", self.db)
+
+class Table(object):
+    """
+    Implements Table interface.
+    """
+    def __init__(self, funcs):
+        self.funcs = funcs
+
+    def cursor(self):
+        """
+        Returns a Cursor for this Table.
+        """
+        return self.funcs["cursor_func"]()
+
+    def put(key, data, txn=None):
+        self[key] = data
+
+class Map(dict):
+    """
+    Implements the map API for person_map, etc.
+    
+    Takes a Table() as argument.
+    """
+    def __init__(self, tbl, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.db = tbl
+
+class MetaCursor(object):
+    def __init__(self):
+        pass
+    def __enter__(self):
+        return self
+    def __iter__(self):
+        return self.__next__()
+    def __next__(self):
+        yield None
+    def __exit__(self, *args, **kwargs):
+        pass
+    def iter(self):
+        yield None
+    def first(self):
+        self._iter = self.__iter__()
+        return self.next()
+    def next(self):
+        try:
+            return next(self._iter)
+        except:
+            return None
+    def close(self):
+        pass
+
 class Cursor(object):
-    """
-    Iterates through model returning (handle, raw_data)...
-    """
-    def __init__(self, model, func):
-        self.model = model
+    def __init__(self, map, func):
+        self.map = map
         self.func = func
     def __enter__(self):
         return self
     def __iter__(self):
-        return self
+        return self.__next__()
     def __next__(self):
-        for handle in self.model.keys():
-            return (handle, self.func(handle))
-    def next(self):
-        for handle in self.model.keys():
-            return (handle, self.func(handle))
+        for item in self.map.keys():
+            yield (bytes(item, "utf-8"), self.func(item))
     def __exit__(self, *args, **kwargs):
         pass
+    def iter(self):
+        for item in self.map.keys():
+            yield (bytes(item, "utf-8"), self.func(item))
+        yield None
+    def first(self):
+        self._iter = self.__iter__()
+        return self.next()
+    def next(self):
+        try:
+            return next(self._iter)
+        except:
+            return None
     def close(self):
         pass
 
-class Bookmarks:
+class Bookmarks(object):
+    def __init__(self):
+        self.handles = []
     def get(self):
-        return [] # handles
+        return self.handles
     def append(self, handle):
-        pass
+        self.handles.append(handle)
 
 class DictionaryTxn(DbTxn):
     def __init__(self, message, db, batch=False):
@@ -99,13 +169,38 @@ class DictionaryTxn(DbTxn):
         """
         txn[handle] = new_data
 
-class DictionaryDb(DbWriteBase, DbReadBase):
+class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     """
     A Gramps Database Backend. This replicates the grampsdb functions.
     """
+    __signals__ = dict((obj+'-'+op, signal)
+                       for obj in
+                       ['person', 'family', 'event', 'place',
+                        'source', 'citation', 'media', 'note', 'repository', 'tag']
+                       for op, signal in zip(
+                               ['add',   'update', 'delete', 'rebuild'],
+                               [(list,), (list,),  (list,),   None]
+                       )
+                   )
+    
+    # 2. Signals for long operations
+    __signals__.update(('long-op-'+op, signal) for op, signal in zip(
+        ['start',  'heartbeat', 'end'],
+        [(object,), None,       None]
+        ))
+
+    # 3. Special signal for change in home person
+    __signals__['home-person-changed'] = None
+
+    # 4. Signal for change in person group name, parameters are
+    __signals__['person-groupname-rebuild'] = (str, str)
+
+    __callback_map = {}
+
     def __init__(self, *args, **kwargs):
         DbReadBase.__init__(self)
         DbWriteBase.__init__(self)
+        Callback.__init__(self)
         self._tables['Person'].update(
             {
                 "handle_func": self.get_person_from_handle, 
@@ -115,6 +210,7 @@ class DictionaryDb(DbWriteBase, DbReadBase):
                 "handles_func": self.get_person_handles,
                 "add_func": self.add_person,
                 "commit_func": self.commit_person,
+                "iter_func": self.iter_people,
             })
         self._tables['Family'].update(
             {
@@ -125,6 +221,7 @@ class DictionaryDb(DbWriteBase, DbReadBase):
                 "handles_func": self.get_family_handles,
                 "add_func": self.add_family,
                 "commit_func": self.commit_family,
+                "iter_func": self.iter_families,
             })
         self._tables['Source'].update(
             {
@@ -135,6 +232,7 @@ class DictionaryDb(DbWriteBase, DbReadBase):
                 "handles_func": self.get_source_handles,
                 "add_func": self.add_source,
                 "commit_func": self.commit_source,
+                "iter_func": self.iter_sources,
                 })
         self._tables['Citation'].update(
             {
@@ -145,6 +243,7 @@ class DictionaryDb(DbWriteBase, DbReadBase):
                 "handles_func": self.get_citation_handles,
                 "add_func": self.add_citation,
                 "commit_func": self.commit_citation,
+                "iter_func": self.iter_citations,
             })
         self._tables['Event'].update(
             {
@@ -155,6 +254,7 @@ class DictionaryDb(DbWriteBase, DbReadBase):
                 "handles_func": self.get_event_handles,
                 "add_func": self.add_event,
                 "commit_func": self.commit_event,
+                "iter_func": self.iter_events,
             })
         self._tables['Media'].update(
             {
@@ -165,6 +265,7 @@ class DictionaryDb(DbWriteBase, DbReadBase):
                 "handles_func": self.get_media_object_handles,
                 "add_func": self.add_object,
                 "commit_func": self.commit_media_object,
+                "iter_func": self.iter_media_objects,
             })
         self._tables['Place'].update(
             {
@@ -175,6 +276,7 @@ class DictionaryDb(DbWriteBase, DbReadBase):
                 "handles_func": self.get_place_handles,
                 "add_func": self.add_place,
                 "commit_func": self.commit_place,
+                "iter_func": self.iter_places,
             })
         self._tables['Repository'].update(
             {
@@ -185,6 +287,7 @@ class DictionaryDb(DbWriteBase, DbReadBase):
                 "handles_func": self.get_repository_handles,
                 "add_func": self.add_repository,
                 "commit_func": self.commit_repository,
+                "iter_func": self.iter_repositories,
             })
         self._tables['Note'].update(
             {
@@ -195,6 +298,7 @@ class DictionaryDb(DbWriteBase, DbReadBase):
                 "handles_func": self.get_note_handles,
                 "add_func": self.add_note,
                 "commit_func": self.commit_note,
+                "iter_func": self.iter_notes,
             })
         self._tables['Tag'].update(
             {
@@ -205,6 +309,7 @@ class DictionaryDb(DbWriteBase, DbReadBase):
                 "handles_func": self.get_tag_handles,
                 "add_func": self.add_tag,
                 "commit_func": self.commit_tag,
+                "iter_func": self.iter_tags,
             })
         # skip GEDCOM cross-ref check for now:
         self.set_feature("skip-check-xref", True)
@@ -218,7 +323,7 @@ class DictionaryDb(DbWriteBase, DbReadBase):
         self.place_bookmarks = Bookmarks()
         self.citation_bookmarks = Bookmarks()
         self.source_bookmarks = Bookmarks()
-        self.repo_bookmarks = Bookmarks()
+        self.repository_bookmarks = Bookmarks()
         self.media_bookmarks = Bookmarks()
         self.note_bookmarks = Bookmarks()
         self.set_person_id_prefix('I%04d')
@@ -249,18 +354,18 @@ class DictionaryDb(DbWriteBase, DbReadBase):
         self.omap_index = 0
         self.rmap_index = 0
         self.nmap_index = 0
-        self.env = None
-        self.person_map      = {}
-        self.family_map      = {}
-        self.place_map       = {}
-        self.citation_map    = {}
-        self.source_map      = {}
-        self.repository_map  = {}
-        self.note_map        = {}
-        self.media_map       = {}
-        self.event_map       = {}
-        self.tag_map         = {}
-        self.metadata   = {}
+        self.env = Environment(self)
+        self.person_map = Map(Table(self._tables["Person"]))
+        self.family_map = Map(Table(self._tables["Family"]))
+        self.place_map  = Map(Table(self._tables["Place"]))
+        self.citation_map = Map(Table(self._tables["Citation"]))
+        self.source_map = Map(Table(self._tables["Source"]))
+        self.repository_map  = Map(Table(self._tables["Repository"]))
+        self.note_map = Map(Table(self._tables["Note"]))
+        self.media_map  = Map(Table(self._tables["Media"]))
+        self.event_map  = Map(Table(self._tables["Event"]))
+        self.tag_map  = Map(Table(self._tables["Tag"]))
+        self.metadata   = Map(Table({"cursor_func": lambda: MetaCursor()}))
         self.name_group = {}
         self.undo_callback = None
         self.redo_callback = None
@@ -286,9 +391,6 @@ class DictionaryDb(DbWriteBase, DbReadBase):
     def transaction_commit(self, txn):
         pass
 
-    def enable_signals(self):
-        pass
-
     def get_undodb(self):
         return None
 
@@ -546,91 +648,107 @@ class DictionaryDb(DbWriteBase, DbReadBase):
         return obj
 
     def get_person_handles(self, sort_handles=False):
-        if sort_handles:
-            raise Exception("Implement!")
-        else:
-            return self.person_map.keys()
+        ## Fixme: implement sort
+        return self.person_map.keys()
 
     def get_family_handles(self):
         return self.family_map.keys()
 
-    def get_event_handles(self):
+    def get_event_handles(self, sort_handles=False):
         return self.event_map.keys()
 
     def get_citation_handles(self, sort_handles=False):
-        if sort_handles:
-            raise Exception("Implement!")
-        else:
-            return self.citation_map.keys()
+        ## Fixme: implement sort
+        return self.citation_map.keys()
 
     def get_source_handles(self, sort_handles=False):
-        if sort_handles:
-            raise Exception("Implement!")
-        else:
-            return self.source_map.keys()
+        ## Fixme: implement sort
+        return self.source_map.keys()
 
     def get_place_handles(self, sort_handles=False):
-        if sort_handles:
-            raise Exception("Implement!")
-        else:
-            return self.place_map.keys()
+        ## Fixme: implement sort
+        return self.place_map.keys()
 
     def get_repository_handles(self):
         return self.repository_map.keys()
 
     def get_media_object_handles(self, sort_handles=False):
-        if sort_handles:
-            raise Exception("Implement!")
-        else:
-            return self.media_map.keys()
+        ## Fixme: implement sort
+        return self.media_map.keys()
 
     def get_note_handles(self):
         return self.note_map.keys()
 
     def get_tag_handles(self, sort_handles=False):
-        if sort_handles:
-            raise Exception("Implement!")
-        else:
-            return self.tag_map.keys()
+        # FIXME: sort
+        return self.tag_map.keys()
 
     def get_event_from_handle(self, handle):
-        return self.event_map[handle]
+        event = None
+        if handle in self.event_map:
+            event = self.event_map[handle]
+        return event
 
     def get_family_from_handle(self, handle): 
-        return self.family_map[handle]
+        family = None
+        if handle in self.family_map:
+            family = self.family_map[handle]
+        return family
 
     def get_repository_from_handle(self, handle):
-        return self.repository_map[handle]
+        repository = None
+        if handle in self.repository_map:
+            repository = self.repository_map[handle]
+        return repository
 
     def get_person_from_handle(self, handle):
-        return self.person_map[handle]
+        person = None
+        if handle in self.person_map:
+            person = self.person_map[handle]
+        return person
 
     def get_place_from_handle(self, handle):
-        place = self.place_map[handle]
+        place = None
+        if handle in self.place_map:
+            place = self.place_map[handle]
         return place
 
     def get_citation_from_handle(self, handle):
-        citation = self.citation_map[handle]
+        citation = None
+        if handle in self.citation_map:
+            citation = self.citation_map[handle]
         return citation
 
     def get_source_from_handle(self, handle):
-        source = self.source_map[handle]
+        source = None
+        if handle in self.source_map:
+            source = self.source_map[handle]
         return source
 
     def get_note_from_handle(self, handle):
-        note = self.note_map[handle]
+        note = None
+        if handle in self.note_map:
+            note = self.note_map[handle]
         return note
 
     def get_object_from_handle(self, handle):
-        media = self.media_map[handle]
+        media = None
+        if handle in self.media_map:
+            media = self.media_map[handle]
         return media
 
     def get_tag_from_handle(self, handle):
-        tag = self.tag_map[handle]
+        tag = None
+        if handle in self.tag_map:
+            tag = self.tag_map[handle]
         return tag
 
     def get_default_person(self):
-        return None
+        handle = self.get_default_handle()
+        if handle:
+            return self.get_person_from_handle(handle)
+        else:
+            return None
 
     def iter_people(self):
         return (person for person in self.person_map.values())
@@ -1203,3 +1321,205 @@ class DictionaryDb(DbWriteBase, DbReadBase):
                 transaction.add(REFERENCE_KEY, TXNDEL, key, old_data, None)
                 #transaction.reference_del.append(str(key))
             self.reference_map.delete(key, txn=txn)
+
+    ## Missing:
+
+    def backup(self):
+        pass
+
+    def close(self):
+        pass
+
+    def find_backlink_handles(self, handle, include_classes=None):
+        return []
+
+    def find_initial_person(self):
+        items = self.person_map.keys()
+        if len(items) > 0:
+            return self.get_person_from_handle(list(items)[0])
+        return None
+
+    def find_place_child_handles(self, handle):
+        return []
+
+    def get_bookmarks(self):
+        return self.bookmarks
+
+    def get_child_reference_types(self):
+        return []
+
+    def get_citation_bookmarks(self):
+        return self.citation_bookmarks
+
+    def get_cursor(self, table, txn=None, update=False, commit=False):
+        pass
+
+    def get_dbname(self):
+        return "DictionaryDb"
+
+    def get_default_handle(self):
+        items = self.person_map.keys()
+        if len(items) > 0:
+            return list(items)[0]
+        return None
+
+    def get_event_attribute_types(self):
+        return []
+
+    def get_event_bookmarks(self):
+        return self.event_bookmarks
+
+    def get_event_roles(self):
+        return []
+
+    def get_event_types(self):
+        return []
+
+    def get_family_attribute_types(self):
+        return []
+
+    def get_family_bookmarks(self):
+        return self.family_bookmarks
+
+    def get_family_event_types(self):
+        return []
+
+    def get_family_relation_types(self):
+        return []
+
+    def get_media_attribute_types(self):
+        return []
+
+    def get_media_bookmarks(self):
+        return self.media_bookmarks
+
+    def get_name_types(self):
+        return []
+
+    def get_note_bookmarks(self):
+        return self.note_bookmarks
+
+    def get_note_types(self):
+        return []
+
+    def get_number_of_records(self, table):
+        return 0
+
+    def get_origin_types(self):
+        return []
+
+    def get_person_attribute_types(self):
+        return []
+
+    def get_person_event_types(self):
+        return []
+
+    def get_place_bookmarks(self):
+        return self.place_bookmarks
+
+    def get_place_tree_cursor(self):
+        return []
+
+    def get_place_types(self):
+        return []
+
+    def get_repo_bookmarks(self):
+        return self.repository_bookmarks
+
+    def get_repository_types(self):
+        return []
+
+    def get_save_path(self):
+        return self._directory
+
+    def get_source_attribute_types(self):
+        return []
+
+    def get_source_bookmarks(self):
+        return self.source_bookmarks
+
+    def get_source_media_types(self):
+        return []
+
+    def get_surname_list(self):
+        return []
+
+    def get_url_types(self):
+        return []
+
+    def has_changed(self):
+        return True
+
+    def is_open(self):
+        return True
+
+    def iter_citation_handles(self):
+        return (key for key in self.citation_map.keys())
+
+    def iter_citations(self):
+        return (key for key in self.citation_map.values())
+
+    def iter_event_handles(self):
+        return (key for key in self.event_map.keys())
+
+    def iter_events(self):
+        return (key for key in self.event_map.values())
+
+    def iter_media_objects(self):
+        return (key for key in self.media_map.values())
+
+    def iter_note_handles(self):
+        return (key for key in self.note_map.keys())
+
+    def iter_notes(self):
+        return (key for key in self.note_map.values())
+
+    def iter_place_handles(self):
+        return (key for key in self.place_map.keys())
+
+    def iter_places(self):
+        return (key for key in self.place_map.values())
+
+    def iter_repositories(self):
+        return (key for key in self.repositories_map.values())
+
+    def iter_repository_handles(self):
+        return (key for key in self.repositories_map.keys())
+
+    def iter_source_handles(self):
+        return (key for key in self.source_map.keys())
+
+    def iter_sources(self):
+        return (key for key in self.source_map.values())
+
+    def iter_tag_handles(self):
+        return (key for key in self.tag_map.keys())
+
+    def iter_tags(self):
+        return (key for key in self.tag_map.values())
+
+    def load(self, directory, pulse_progress, mode, 
+             force_schema_upgrade, 
+             force_bsddb_upgrade, 
+             force_bsddb_downgrade, 
+             force_python_upgrade):
+        self._directory = directory
+
+    def prepare_import(self):
+        pass
+
+    def redo(self, update_history=True):
+        pass
+
+    def restore(self):
+        pass
+
+    def set_prefixes(self, person, media, family, source, citation, 
+                     place, event, repository, note):
+        pass
+
+    def set_save_path(self, directory):
+        self._directory = directory
+
+    def undo(self, update_history=True):
+        pass
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index e0d8c043a..ee6d4162d 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -68,7 +68,7 @@ class Environment(object):
         self.db = db
 
     def txn_begin(self):
-        return DjangoTxn("DbDjango Transaction", self.db)
+        return DjangoTxn("DjangoDb Transaction", self.db)
 
 class Table(object):
     """
@@ -210,6 +210,8 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     # 4. Signal for change in person group name, parameters are
     __signals__['person-groupname-rebuild'] = (str, str)
 
+    __callback_map = {}
+
     def __init__(self, directory=None):
         DbReadBase.__init__(self)
         DbWriteBase.__init__(self)
@@ -1745,8 +1747,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 self.get_number_of_media_objects() == 0 and
                 self.get_number_of_repositories() == 0)
                 
-    __callback_map = {}
-
     def set_prefixes(self, person, media, family, source, citation,
                      place, event, repository, note):
         self.set_person_id_prefix(person)
diff --git a/gramps/webapp/utils.py b/gramps/webapp/utils.py
index 07eddd341..10e0aaf38 100644
--- a/gramps/webapp/utils.py
+++ b/gramps/webapp/utils.py
@@ -143,8 +143,12 @@ def get_person_from_handle(db, handle):
         return None
 
 def probably_alive(handle):
+    ## FIXME: need to call after save?
     person = db.get_person_from_handle(handle)
-    return alive(person, db)
+    if person:
+        return alive(person, db)
+    else:
+        return True
 
 def format_number(number, with_grouping=True):
     if number != "":

From 789158aca5a0b3cab5817386e1f0bf32d06d9731 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Wed, 13 May 2015 21:29:07 -0400
Subject: [PATCH 058/105] DictionaryDb: now reads/writes on open/close

---
 gramps/gen/db/dbconst.py                      |  58 +++++--
 .../plugins/database/bsddb_support/write.py   |  33 ----
 gramps/plugins/database/dictionarydb.py       | 142 +++++++++++++++---
 gramps/plugins/database/djangodb.py           |   6 -
 gramps/plugins/importer/importxml.py          |   2 +-
 5 files changed, 170 insertions(+), 71 deletions(-)

diff --git a/gramps/gen/db/dbconst.py b/gramps/gen/db/dbconst.py
index e53535248..21a17db80 100644
--- a/gramps/gen/db/dbconst.py
+++ b/gramps/gen/db/dbconst.py
@@ -28,20 +28,16 @@ Declare constants used by database modules
 # constants
 #
 #-------------------------------------------------------------------------
-__all__ = (
-            ('DBPAGE', 'DBMODE', 'DBCACHE', 'DBLOCKS', 'DBOBJECTS', 'DBUNDO',
-             'DBEXT', 'DBMODE_R', 'DBMODE_W', 'DBUNDOFN', 'DBLOCKFN',
-             'DBRECOVFN','BDBVERSFN', 'DBLOGNAME', 'SCHVERSFN', 'PCKVERSFN',
-             'DBBACKEND'
-            ) +
-            
-            ('PERSON_KEY', 'FAMILY_KEY', 'SOURCE_KEY', 'CITATION_KEY',
-             'EVENT_KEY', 'MEDIA_KEY', 'PLACE_KEY', 'REPOSITORY_KEY',
-             'NOTE_KEY', 'REFERENCE_KEY', 'TAG_KEY'
-            ) +
-
-            ('TXNADD', 'TXNUPD', 'TXNDEL')
-          )
+__all__ = ( 'DBPAGE', 'DBMODE', 'DBCACHE', 'DBLOCKS', 'DBOBJECTS', 'DBUNDO',
+            'DBEXT', 'DBMODE_R', 'DBMODE_W', 'DBUNDOFN', 'DBLOCKFN',
+            'DBRECOVFN','BDBVERSFN', 'DBLOGNAME', 'SCHVERSFN', 'PCKVERSFN',
+            'DBBACKEND',
+            'PERSON_KEY', 'FAMILY_KEY', 'SOURCE_KEY', 'CITATION_KEY',
+            'EVENT_KEY', 'MEDIA_KEY', 'PLACE_KEY', 'REPOSITORY_KEY',
+            'NOTE_KEY', 'REFERENCE_KEY', 'TAG_KEY',
+            'TXNADD', 'TXNUPD', 'TXNDEL',
+            "CLASS_TO_KEY_MAP", "KEY_TO_CLASS_MAP", "KEY_TO_NAME_MAP"
+        )
 
 DBEXT     = ".db"           # File extension to be used for database files
 DBUNDOFN  = "undo.db"       # File name of 'undo' database
@@ -74,3 +70,37 @@ TAG_KEY        = 9
 CITATION_KEY   = 10
 
 TXNADD, TXNUPD, TXNDEL = 0, 1, 2
+
+CLASS_TO_KEY_MAP = {"Person": PERSON_KEY, 
+                    "Family": FAMILY_KEY, 
+                    "Source": SOURCE_KEY, 
+                    "Citation": CITATION_KEY, 
+                    "Event": EVENT_KEY, 
+                    "MediaObject": MEDIA_KEY, 
+                    "Place": PLACE_KEY, 
+                    "Repository": REPOSITORY_KEY,
+                    "Note" : NOTE_KEY,
+                    "Tag": TAG_KEY}
+
+KEY_TO_CLASS_MAP = {PERSON_KEY: "Person", 
+                    FAMILY_KEY: "Family", 
+                    SOURCE_KEY: "Source", 
+                    CITATION_KEY: "Citation", 
+                    EVENT_KEY: "Event", 
+                    MEDIA_KEY: "MediaObject", 
+                    PLACE_KEY: "Place", 
+                    REPOSITORY_KEY: "Repository",
+                    NOTE_KEY: "Note",
+                    TAG_KEY: "Tag"}
+
+KEY_TO_NAME_MAP = {PERSON_KEY: 'person',
+                   FAMILY_KEY: 'family',
+                   EVENT_KEY: 'event',
+                   SOURCE_KEY: 'source',
+                   CITATION_KEY: 'citation',
+                   PLACE_KEY: 'place',
+                   MEDIA_KEY: 'media',
+                   REPOSITORY_KEY: 'repository',
+                   #REFERENCE_KEY: 'reference',
+                   NOTE_KEY: 'note',
+                   TAG_KEY: 'tag'}
diff --git a/gramps/plugins/database/bsddb_support/write.py b/gramps/plugins/database/bsddb_support/write.py
index 606f9484a..66d4e4a53 100644
--- a/gramps/plugins/database/bsddb_support/write.py
+++ b/gramps/plugins/database/bsddb_support/write.py
@@ -130,39 +130,6 @@ DBERRS      = (db.DBRunRecoveryError, db.DBAccessError,
 # these maps or modifying the values of the keys will break
 # existing databases.
 
-CLASS_TO_KEY_MAP = {Person.__name__: PERSON_KEY, 
-                    Family.__name__: FAMILY_KEY, 
-                    Source.__name__: SOURCE_KEY, 
-                    Citation.__name__: CITATION_KEY, 
-                    Event.__name__: EVENT_KEY, 
-                    MediaObject.__name__: MEDIA_KEY, 
-                    Place.__name__: PLACE_KEY, 
-                    Repository.__name__:REPOSITORY_KEY,
-                    Note.__name__: NOTE_KEY,
-                    Tag.__name__: TAG_KEY}
-
-KEY_TO_CLASS_MAP = {PERSON_KEY: Person.__name__, 
-                    FAMILY_KEY: Family.__name__, 
-                    SOURCE_KEY: Source.__name__, 
-                    CITATION_KEY: Citation.__name__, 
-                    EVENT_KEY: Event.__name__, 
-                    MEDIA_KEY: MediaObject.__name__, 
-                    PLACE_KEY: Place.__name__, 
-                    REPOSITORY_KEY: Repository.__name__,
-                    NOTE_KEY: Note.__name__,
-                    TAG_KEY: Tag.__name__}
-
-KEY_TO_NAME_MAP = {PERSON_KEY: 'person',
-                   FAMILY_KEY: 'family',
-                   EVENT_KEY: 'event',
-                   SOURCE_KEY: 'source',
-                   CITATION_KEY: 'citation',
-                   PLACE_KEY: 'place',
-                   MEDIA_KEY: 'media',
-                   REPOSITORY_KEY: 'repository',
-                   #REFERENCE_KEY: 'reference',
-                   NOTE_KEY: 'note',
-                   TAG_KEY: 'tag'}
 #-------------------------------------------------------------------------
 #
 # Helper functions
diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 621425e2d..09e2451b6 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -28,6 +28,7 @@ import pickle
 import base64
 import time
 import re
+import os
 from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn
 from gramps.gen.utils.callback import Callback
 from gramps.gen.updatecallback import UpdateCallback
@@ -40,7 +41,8 @@ from gramps.gen.db import (PERSON_KEY,
                            PLACE_KEY,
                            REPOSITORY_KEY,
                            NOTE_KEY,
-                           TAG_KEY)
+                           TAG_KEY,
+                           KEY_TO_NAME_MAP)
 
 from gramps.gen.utils.id import create_id
 from gramps.gen.lib.researcher import Researcher
@@ -197,7 +199,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     __callback_map = {}
 
-    def __init__(self, *args, **kwargs):
+    def __init__(self, directory=None):
         DbReadBase.__init__(self)
         DbWriteBase.__init__(self)
         Callback.__init__(self)
@@ -323,7 +325,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.place_bookmarks = Bookmarks()
         self.citation_bookmarks = Bookmarks()
         self.source_bookmarks = Bookmarks()
-        self.repository_bookmarks = Bookmarks()
+        self.repo_bookmarks = Bookmarks()
         self.media_bookmarks = Bookmarks()
         self.note_bookmarks = Bookmarks()
         self.set_person_id_prefix('I%04d')
@@ -373,6 +375,9 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.modified   = 0
         self.txn = DictionaryTxn("DbDictionary Transaction", self)
         self.transaction = None
+        self._directory = directory
+        if directory:
+            self.load(directory)
 
     def version_supported(self):
         """Return True when the file has a supported version."""
@@ -768,24 +773,66 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 return tag
         return None
 
-    def get_family_from_gramps_id(self, gramps_id):
-        for family in self.family_map.values():
-            if family.gramps_id == gramps_id:
-                return family
-        return None
-
     def get_person_from_gramps_id(self, gramps_id):
         for person in self.person_map.values():
             if person.gramps_id == gramps_id:
                 return person
         return None
 
+    def get_family_from_gramps_id(self, gramps_id):
+        for family in self.family_map.values():
+            if family.gramps_id == gramps_id:
+                return family
+        return None
+
+    def get_citation_from_gramps_id(self, gramps_id):
+        for citation in self.citation_map.values():
+            if citation.gramps_id == gramps_id:
+                return citation
+        return None
+
+    def get_source_from_gramps_id(self, gramps_id):
+        for source in self.source_map.values():
+            if source.gramps_id == gramps_id:
+                return source
+        return None
+
+    def get_event_from_gramps_id(self, gramps_id):
+        for event in self.event_map.values():
+            if event.gramps_id == gramps_id:
+                return event
+        return None
+
+    def get_media_from_gramps_id(self, gramps_id):
+        for media in self.media_map.values():
+            if media.gramps_id == gramps_id:
+                return media
+        return None
+
     def get_place_from_gramps_id(self, gramps_id):
         for place in self.place_map.values():
             if place.gramps_id == gramps_id:
                 return place
         return None
 
+    def get_repository_from_gramps_id(self, gramps_id):
+        for repository in self.repository_map.values():
+            if repository.gramps_id == gramps_id:
+                return repository
+        return None
+
+    def get_note_from_gramps_id(self, gramps_id):
+        for note in self.note_map.values():
+            if note.gramps_id == gramps_id:
+                return note
+        return None
+
+    def get_tag_from_gramps_id(self, gramps_id):
+        for tag in self.tag_map.values():
+            if tag.gramps_id == gramps_id:
+                return tag
+        return None
+
     def get_number_of_people(self):
         return len(self.person_map)
 
@@ -1038,33 +1085,73 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return obj.handle
 
     def commit_person(self, person, trans, change_time=None):
+        if person.handle in self.person_map:
+            self.emit("person-update", ([person.handle],))
+        else:
+            self.emit("person-add", ([person.handle],))
         self.person_map[person.handle] = person
 
     def commit_family(self, family, trans, change_time=None):
+        if family.handle in self.family_map:
+            self.emit("family-update", ([family.handle],))
+        else:
+            self.emit("family-add", ([family.handle],))
         self.family_map[family.handle] = family
 
     def commit_citation(self, citation, trans, change_time=None):
+        if citation.handle in self.citation_map:
+            self.emit("citation-update", ([citation.handle],))
+        else:
+            self.emit("citation-add", ([citation.handle],))
         self.citation_map[citation.handle] = citation
 
     def commit_source(self, source, trans, change_time=None):
+        if source.handle in self.source_map:
+            self.emit("source-update", ([source.handle],))
+        else:
+            self.emit("source-add", ([source.handle],))
         self.source_map[source.handle] = source
 
     def commit_repository(self, repository, trans, change_time=None):
+        if repository.handle in self.repository_map:
+            self.emit("repository-update", ([repository.handle],))
+        else:
+            self.emit("repository-add", ([repository.handle],))
         self.repository_map[repository.handle] = repository
 
     def commit_note(self, note, trans, change_time=None):
+        if note.handle in self.note_map:
+            self.emit("note-update", ([note.handle],))
+        else:
+            self.emit("note-add", ([note.handle],))
         self.note_map[note.handle] = note
 
     def commit_place(self, place, trans, change_time=None):
+        if place.handle in self.place_map:
+            self.emit("place-update", ([place.handle],))
+        else:
+            self.emit("place-add", ([place.handle],))
         self.place_map[place.handle] = place
 
     def commit_event(self, event, trans, change_time=None):
+        if event.handle in self.event_map:
+            self.emit("event-update", ([event.handle],))
+        else:
+            self.emit("event-add", ([event.handle],))
         self.event_map[event.handle] = event
 
     def commit_tag(self, tag, trans, change_time=None):
+        if tag.handle in self.tag_map:
+            self.emit("tag-update", ([tag.handle],))
+        else:
+            self.emit("tag-add", ([tag.handle],))
         self.tag_map[tag.handle] = tag
 
     def commit_media_object(self, obj, transaction, change_time=None):
+        if commit.handle in self.commit_map:
+            self.emit("commit-update", ([commit.handle],))
+        else:
+            self.emit("commit-add", ([commit.handle],))
         self.media_map[obj.handle] = obj
 
     def get_gramps_ids(self, obj_key):
@@ -1092,7 +1179,16 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         pass
 
     def request_rebuild(self):
-        pass
+        self.emit('person-rebuild')
+        self.emit('family-rebuild')
+        self.emit('place-rebuild')
+        self.emit('source-rebuild')
+        self.emit('citation-rebuild')
+        self.emit('media-rebuild')
+        self.emit('event-rebuild')
+        self.emit('repository-rebuild')
+        self.emit('note-rebuild')
+        self.emit('tag-rebuild')
 
     def copy_from_db(self, db):
         """
@@ -1163,6 +1259,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.delete_primary_from_reference_map(handle, transaction,
                                                    txn=self.txn)
             self.person_map.delete(handle, txn=self.txn)
+            self.emit("person-delete", ([handle],))
             transaction.add(PERSON_KEY, TXNDEL, handle, person.serialize(), None)
 
     def remove_source(self, handle, transaction):
@@ -1262,6 +1359,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                                    txn=self.txn)
             old_data = data_map.get(handle, txn=self.txn)
             data_map.delete(handle, txn=self.txn)
+            self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],))
             transaction.add(key, TXNDEL, handle, old_data, None)
 
     def delete_primary_from_reference_map(self, handle, transaction, txn=None):
@@ -1328,7 +1426,12 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         pass
 
     def close(self):
-        pass
+        if self._directory:
+            from gramps.plugins.export.exportxml import XmlWriter
+            from gramps.cli.user import User 
+            writer = XmlWriter(self, User(), strip_photos=0, compress=1)
+            filename = os.path.join(self._directory, "data.gramps")
+            writer.write(filename)
 
     def find_backlink_handles(self, handle, include_classes=None):
         return []
@@ -1424,7 +1527,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return []
 
     def get_repo_bookmarks(self):
-        return self.repository_bookmarks
+        return self.repo_bookmarks
 
     def get_repository_types(self):
         return []
@@ -1498,12 +1601,17 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def iter_tags(self):
         return (key for key in self.tag_map.values())
 
-    def load(self, directory, pulse_progress, mode, 
-             force_schema_upgrade, 
-             force_bsddb_upgrade, 
-             force_bsddb_downgrade, 
-             force_python_upgrade):
+    def load(self, directory, pulse_progress=None, mode=None, 
+             force_schema_upgrade=False, 
+             force_bsddb_upgrade=False, 
+             force_bsddb_downgrade=False, 
+             force_python_upgrade=False):
+        from gramps.plugins.importer.importxml import importData
+        from gramps.cli.user import User 
         self._directory = directory
+        filename = os.path.join(directory, "data.gramps")
+        if os.path.isfile(filename):
+            importData(self, filename, User())
 
     def prepare_import(self):
         pass
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index ee6d4162d..56f40f5ac 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -1402,7 +1402,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not person.gramps_id and set_gid:
             person.gramps_id = self.find_next_person_gramps_id()
         self.commit_person(person, trans)
-        self.emit("person-add", ([person.handle],))
         return person.handle
 
     def add_family(self, family, trans, set_gid=True):
@@ -1411,7 +1410,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not family.gramps_id and set_gid:
             family.gramps_id = self.find_next_family_gramps_id()
         self.commit_family(family, trans)
-        self.emit("family-add", ([family.handle],))
         return family.handle
 
     def add_citation(self, citation, trans, set_gid=True):
@@ -1420,7 +1418,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not citation.gramps_id and set_gid:
             citation.gramps_id = self.find_next_citation_gramps_id()
         self.commit_citation(citation, trans)
-        self.emit("citation-add", ([citation.handle],))
         return citation.handle
 
     def add_source(self, source, trans, set_gid=True):
@@ -1429,7 +1426,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not source.gramps_id and set_gid:
             source.gramps_id = self.find_next_source_gramps_id()
         self.commit_source(source, trans)
-        self.emit("source-add", ([source.handle],))
         return source.handle
 
     def add_repository(self, repository, trans, set_gid=True):
@@ -1438,7 +1434,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not repository.gramps_id and set_gid:
             repository.gramps_id = self.find_next_repository_gramps_id()
         self.commit_repository(repository, trans)
-        self.emit("repository-add", ([repository.handle],))
         return repository.handle
 
     def add_note(self, note, trans, set_gid=True):
@@ -1447,7 +1442,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not note.gramps_id and set_gid:
             note.gramps_id = self.find_next_note_gramps_id()
         self.commit_note(note, trans)
-        self.emit("note-add", ([note.handle],))
         return note.handle
 
     def add_place(self, place, trans, set_gid=True):
diff --git a/gramps/plugins/importer/importxml.py b/gramps/plugins/importer/importxml.py
index 00aa28420..39a3f04d4 100644
--- a/gramps/plugins/importer/importxml.py
+++ b/gramps/plugins/importer/importxml.py
@@ -57,7 +57,7 @@ from gramps.gen.lib import (Address, Attribute, AttributeType, ChildRef,
                             SrcAttribute, SrcAttributeType, StyledText,
                             StyledTextTag, StyledTextTagType, Surname, Tag, Url)
 from gramps.gen.db import DbTxn
-from gramps.gen.db.write import CLASS_TO_KEY_MAP
+#from gramps.gen.db.write import CLASS_TO_KEY_MAP
 from gramps.gen.errors import GrampsImportError
 from gramps.gen.utils.id import create_id
 from gramps.gen.utils.db import family_name

From 3489276fa1a5fa74d9af2945c6f2c5d231b5c619 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Wed, 13 May 2015 22:01:47 -0400
Subject: [PATCH 059/105] Moved key maps to dbconst

---
 gramps/plugins/database/dictionarydb.py | 15 +++++++--------
 gramps/plugins/importer/importxml.py    |  2 +-
 2 files changed, 8 insertions(+), 9 deletions(-)

diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 09e2451b6..eab579416 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -29,7 +29,7 @@ import base64
 import time
 import re
 import os
-from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn
+from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn, KEY_TO_NAME_MAP
 from gramps.gen.utils.callback import Callback
 from gramps.gen.updatecallback import UpdateCallback
 from gramps.gen.db import (PERSON_KEY,
@@ -41,8 +41,7 @@ from gramps.gen.db import (PERSON_KEY,
                            PLACE_KEY,
                            REPOSITORY_KEY,
                            NOTE_KEY,
-                           TAG_KEY,
-                           KEY_TO_NAME_MAP)
+                           TAG_KEY)
 
 from gramps.gen.utils.id import create_id
 from gramps.gen.lib.researcher import Researcher
@@ -1147,12 +1146,12 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.emit("tag-add", ([tag.handle],))
         self.tag_map[tag.handle] = tag
 
-    def commit_media_object(self, obj, transaction, change_time=None):
-        if commit.handle in self.commit_map:
-            self.emit("commit-update", ([commit.handle],))
+    def commit_media_object(self, media, transaction, change_time=None):
+        if media.handle in self.media_map:
+            self.emit("media-update", ([media.handle],))
         else:
-            self.emit("commit-add", ([commit.handle],))
-        self.media_map[obj.handle] = obj
+            self.emit("media-add", ([media.handle],))
+        self.media_map[media.handle] = media
 
     def get_gramps_ids(self, obj_key):
         key2table = {
diff --git a/gramps/plugins/importer/importxml.py b/gramps/plugins/importer/importxml.py
index 39a3f04d4..3cbea0785 100644
--- a/gramps/plugins/importer/importxml.py
+++ b/gramps/plugins/importer/importxml.py
@@ -68,7 +68,7 @@ from gramps.gen.display.name import displayer as name_displayer
 from gramps.gen.db.dbconst import (PERSON_KEY, FAMILY_KEY, SOURCE_KEY, 
                                    EVENT_KEY, MEDIA_KEY, PLACE_KEY, 
                                    REPOSITORY_KEY, NOTE_KEY, TAG_KEY, 
-                                   CITATION_KEY)
+                                   CITATION_KEY, CLASS_TO_KEY_MAP)
 from gramps.gen.updatecallback import UpdateCallback
 from gramps.version import VERSION
 from gramps.gen.config import config

From 7ded76695ac935f6317c4aa4c6d382cb5e44c41c Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Wed, 13 May 2015 22:55:23 -0400
Subject: [PATCH 060/105] DictionaryDb: implement delete

---
 gramps/plugins/database/dictionarydb.py | 36 +++++++------------------
 1 file changed, 9 insertions(+), 27 deletions(-)

diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index eab579416..647f6b0ee 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -999,6 +999,11 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             return self.tag_map[handle].serialize()
         return None
 
+    def get_raw_event_data(self, handle):
+        if handle in self.event_map:
+            return self.event_map[handle].serialize()
+        return None
+
     def add_person(self, person, trans, set_gid=True):
         if not person.handle:
             person.handle = create_id()
@@ -1244,22 +1249,9 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
         if self.readonly or not handle:
             return
-        person = self.get_person_from_handle(handle)
-        #self.genderStats.uncount_person (person)
-        #self.remove_from_surname_list(person)
-        if isinstance(handle, str):
-            handle = handle.encode('utf-8')
-        if transaction.batch:
-            with BSDDBTxn(self.env, self.person_map) as txn:            
-                self.delete_primary_from_reference_map(handle, transaction,
-                                                       txn=txn.txn)
-                txn.delete(handle)
-        else:
-            self.delete_primary_from_reference_map(handle, transaction,
-                                                   txn=self.txn)
-            self.person_map.delete(handle, txn=self.txn)
+        if handle in self.person_map:
+            del self.person_map[handle]
             self.emit("person-delete", ([handle],))
-            transaction.add(PERSON_KEY, TXNDEL, handle, person.serialize(), None)
 
     def remove_source(self, handle, transaction):
         """
@@ -1345,21 +1337,11 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def __do_remove(self, handle, transaction, data_map, key):
         if self.readonly or not handle:
             return
-
         if isinstance(handle, str):
             handle = handle.encode('utf-8')
-        if transaction.batch:
-            with BSDDBTxn(self.env, data_map) as txn:
-                self.delete_primary_from_reference_map(handle, transaction,
-                                                        txn=txn.txn)
-                txn.delete(handle)
-        else:
-            self.delete_primary_from_reference_map(handle, transaction,
-                                                   txn=self.txn)
-            old_data = data_map.get(handle, txn=self.txn)
-            data_map.delete(handle, txn=self.txn)
+        if handle in data_map:
+            del data_map[handle]
             self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],))
-            transaction.add(key, TXNDEL, handle, old_data, None)
 
     def delete_primary_from_reference_map(self, handle, transaction, txn=None):
         """

From 61ec1c1b48a5072a271e10cdfa5b15cd03511993 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Thu, 14 May 2015 06:31:59 -0400
Subject: [PATCH 061/105] Database backends: bsddb, django, and dictionary

---
 gramps/cli/clidbman.py                        |  11 +-
 gramps/gui/dbman.py                           |  44 +++--
 gramps/plugins/database/dictionarydb.py       |  20 ++-
 .../defaults/default_settings.py              | 150 ++++++++++++++++++
 .../django_support/defaults/sqlite.db         | Bin 0 -> 293888 bytes
 gramps/plugins/database/djangodb.py           |  23 ++-
 6 files changed, 230 insertions(+), 18 deletions(-)
 create mode 100644 gramps/plugins/database/django_support/defaults/default_settings.py
 create mode 100644 gramps/plugins/database/django_support/defaults/sqlite.db

diff --git a/gramps/cli/clidbman.py b/gramps/cli/clidbman.py
index a4986f615..893a2d84f 100644
--- a/gramps/cli/clidbman.py
+++ b/gramps/cli/clidbman.py
@@ -225,7 +225,7 @@ class CLIDbManager(object):
         """
         print(_('Import finished...'))
 
-    def create_new_db_cli(self, title=None, create_db=True):
+    def create_new_db_cli(self, title=None, create_db=True, dbid=None):
         """
         Create a new database.
         """
@@ -244,8 +244,9 @@ class CLIDbManager(object):
 
         if create_db:
             # write the version number into metadata
-            
-            newdb = self.dbstate.make_database("bsddb")
+            if dbid is None:
+                dbid = "bsddb"
+            newdb = self.dbstate.make_database(dbid)
             newdb.write_version(new_path)
 
         (tval, last) = time_val(new_path)
@@ -254,11 +255,11 @@ class CLIDbManager(object):
                                    last, tval, False, ""))
         return new_path, title
 
-    def _create_new_db(self, title=None):
+    def _create_new_db(self, title=None, dbid=None):
         """
         Create a new database, do extra stuff needed
         """
-        return self.create_new_db_cli(title)
+        return self.create_new_db_cli(title, dbid=dbid)
 
     def import_new_db(self, filename, user):
         """
diff --git a/gramps/gui/dbman.py b/gramps/gui/dbman.py
index b105ac37b..0891170f4 100644
--- a/gramps/gui/dbman.py
+++ b/gramps/gui/dbman.py
@@ -72,7 +72,7 @@ from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
 from gramps.gen.const import URL_WIKISTRING
 from .user import User
-from .dialog import ErrorDialog, QuestionDialog, QuestionDialog2
+from .dialog import ErrorDialog, QuestionDialog, QuestionDialog2, ICON
 from .pluginmanager import GuiPluginManager
 from gramps.cli.clidbman import CLIDbManager, NAME_FILE, time_val
 from .ddtargets import DdTargets
@@ -80,7 +80,6 @@ from gramps.gen.recentfiles import rename_filename, remove_filename
 from .glade import Glade
 from gramps.gen.db.exceptions import DbException
 
-
 _RETURN = Gdk.keyval_from_name("Return")
 _KP_ENTER = Gdk.keyval_from_name("KP_Enter")
 
@@ -104,6 +103,25 @@ ICON_COL = 6
 
 RCS_BUTTON = { True : _('_Extract'), False : _('_Archive') }
 
+class DatabaseDialog(Gtk.MessageDialog):
+    def __init__(self, parent=None):
+        Gtk.MessageDialog.__init__(self,
+                                parent,
+                                flags=Gtk.DialogFlags.MODAL,
+                                type=Gtk.MessageType.QUESTION,
+                                   )
+        self.set_icon(ICON)
+        self.set_title('')
+
+        self.set_markup('<span size="larger" weight="bold">%s</span>' %
+                        _('Database Backend'))
+        self.format_secondary_text(
+            _("Please select a database backend type"))
+
+        self.add_button("BSDDB Database (standard)", 1)
+        self.add_button("Dictionary (in-memory)", 2)
+        self.add_button("Django Database", 3)
+
 class DbManager(CLIDbManager):
     """
     Database Manager. Opens a database manager window that allows users to
@@ -761,19 +779,27 @@ class DbManager(CLIDbManager):
         message.
         """
         self.new.set_sensitive(False)
-        try:
-            self._create_new_db()
-        except (OSError, IOError) as msg:
-            DbManager.ERROR(_("Could not create Family Tree"),
-                                       str(msg))
+        # popup window and ask for dbid types, if more than one
+        ## FIXME: autoload from plugins
+        dbid = "bsddb"
+        d = DatabaseDialog(self.top)
+        database = d.run()
+        d.destroy()
+        if database >= 0:
+            dbid = {1:"bsddb",2:"dictionarydb",3:"djangodb"}[database]
+            try:
+                self._create_new_db(dbid=dbid)
+            except (OSError, IOError) as msg:
+                DbManager.ERROR(_("Could not create Family Tree"),
+                                str(msg))
         self.new.set_sensitive(True)
 
-    def _create_new_db(self, title=None, create_db=True):
+    def _create_new_db(self, title=None, create_db=True, dbid=None):
         """
         Create a new database, append to model
         """
         new_path, title = self.create_new_db_cli(conv_to_unicode(title, 'utf8'),
-                                                 create_db)
+                                                 create_db, dbid)
         path_name = os.path.join(new_path, NAME_FILE)
         (tval, last) = time_val(new_path)
         node = self.model.append(None, [title, new_path, path_name, 
diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 647f6b0ee..936d8f534 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -21,7 +21,7 @@
 
 #------------------------------------------------------------------------
 #
-# Gramps Modules
+# Python Modules
 #
 #------------------------------------------------------------------------
 import pickle
@@ -29,7 +29,15 @@ import base64
 import time
 import re
 import os
+import logging
+
+#------------------------------------------------------------------------
+#
+# Gramps Modules
+#
+#------------------------------------------------------------------------
 from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn, KEY_TO_NAME_MAP
+from gramps.gen.db.dbconst import *
 from gramps.gen.utils.callback import Callback
 from gramps.gen.updatecallback import UpdateCallback
 from gramps.gen.db import (PERSON_KEY,
@@ -56,6 +64,8 @@ from gramps.gen.lib.repo import Repository
 from gramps.gen.lib.note import Note
 from gramps.gen.lib.tag import Tag
 
+_LOG = logging.getLogger(DBLOGNAME)
+
 class Environment(object):
     """
     Implements the Environment API.
@@ -1612,3 +1622,11 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def undo(self, update_history=True):
         pass
+
+    def write_version(self, directory):
+        """Write files for a newly created DB."""
+        versionpath = os.path.join(directory, str(DBBACKEND))
+        _LOG.debug("Write database backend file to 'dictionarydb'")
+        with open(versionpath, "w") as version_file:
+            version_file.write("dictionarydb")
+
diff --git a/gramps/plugins/database/django_support/defaults/default_settings.py b/gramps/plugins/database/django_support/defaults/default_settings.py
new file mode 100644
index 000000000..fae3ffd06
--- /dev/null
+++ b/gramps/plugins/database/django_support/defaults/default_settings.py
@@ -0,0 +1,150 @@
+import os
+from gramps.gen.const import DATA_DIR
+
+WEB_DIR = os.path.dirname(os.path.realpath(__file__))
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+INTERNAL_IPS = ('127.0.0.1',)
+
+ADMINS = (
+    ('admin', 'your_email@domain.com'),
+)
+
+MANAGERS = ADMINS
+DATABASE_ROUTERS = []
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3',
+        'NAME': os.path.join(WEB_DIR, 'sqlite.db'),
+    }
+}
+DATABASE_ENGINE = 'sqlite3'           
+DATABASE_NAME = os.path.join(WEB_DIR, 'sqlite.db')
+DATABASE_USER = ''             
+DATABASE_PASSWORD = ''         
+DATABASE_HOST = ''             
+DATABASE_PORT = ''             
+TIME_ZONE = 'America/New_York'
+LANGUAGE_CODE = 'en-us'
+SITE_ID = 1
+USE_I18N = True
+MEDIA_ROOT = ''
+MEDIA_URL = ''
+ADMIN_MEDIA_PREFIX = '/gramps-media/'
+SECRET_KEY = 'zd@%vslj5sqhx94_8)0hsx*rk9tj3^ly$x+^*tq4bggr&uh$ac'
+
+TEMPLATE_LOADERS = (
+    'django.template.loaders.filesystem.Loader', # 1.4
+    'django.template.loaders.app_directories.Loader', # 1.4
+   #'django.template.loaders.filesystem.load_template_source',
+   #'django.template.loaders.app_directories.load_template_source',
+)
+
+MIDDLEWARE_CLASSES = (
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.middleware.common.CommonMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+#   'debug_toolbar.middleware.DebugToolbarMiddleware',
+)
+
+ROOT_URLCONF = 'gramps.webapp.urls'
+STATIC_URL = '/static/' # 1.4
+
+TEMPLATE_DIRS = (
+    # Use absolute paths, not relative paths.
+    os.path.join(DATA_DIR, "templates"),
+)
+
+TEMPLATE_CONTEXT_PROCESSORS = (
+    "django.contrib.auth.context_processors.auth", # 1.4
+    "django.contrib.messages.context_processors.messages", # 1.4
+#   "django.core.context_processors.auth",
+#   "django.core.context_processors.debug",
+    "django.core.context_processors.i18n",
+    "django.core.context_processors.media",
+    "gramps.webapp.grampsdb.views.context_processor",
+    "gramps.webapp.context.messages",
+)
+
+INSTALLED_APPS = (
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.staticfiles',
+    'django.contrib.messages', # 1.4
+    'django.contrib.sites',
+    'django.contrib.admin',
+    'gramps.webapp.grampsdb',
+#    'django_extensions',
+#    'debug_toolbar',
+)
+
+DEBUG_TOOLBAR_PANELS = (
+    'debug_toolbar.panels.version.VersionDebugPanel',
+    'debug_toolbar.panels.timer.TimerDebugPanel',
+    'debug_toolbar.panels.settings_vars.SettingsVarsDebugPanel',
+    'debug_toolbar.panels.headers.HeaderDebugPanel',
+    'debug_toolbar.panels.request_vars.RequestVarsDebugPanel',
+    'debug_toolbar.panels.template.TemplateDebugPanel',
+    'debug_toolbar.panels.sql.SQLDebugPanel',
+    'debug_toolbar.panels.signals.SignalDebugPanel',
+    'debug_toolbar.panels.logger.LoggingPanel',
+    )
+
+def custom_show_toolbar(request):
+    return True # Always show toolbar, for example purposes only.
+
+DEBUG_TOOLBAR_CONFIG = {
+    'INTERCEPT_REDIRECTS': False,
+#    'SHOW_TOOLBAR_CALLBACK': custom_show_toolbar,
+#    'EXTRA_SIGNALS': ['myproject.signals.MySignal'],
+    'HIDE_DJANGO_SQL': False,
+    }
+
+AUTH_PROFILE_MODULE = "grampsdb.Profile"
+
+# Had to add these to use settings.configure():
+DATABASE_OPTIONS = ''
+URL_VALIDATOR_USER_AGENT = ''
+DEFAULT_INDEX_TABLESPACE = ''
+DEFAULT_TABLESPACE = ''
+CACHE_BACKEND = 'locmem://'
+TRANSACTIONS_MANAGED = False
+LOCALE_PATHS = tuple()
+
+# Changes for Django 1.3:
+USE_L10N = True
+FORMAT_MODULE_PATH = ""
+## End Changes for Django 1.3
+
+# Changes for Django 1.4:
+USE_TZ = False
+## End Changes for Django 1.4
+
+# Changes for Django 1.5:
+CACHES = {
+    'default': {
+        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
+        }
+    }
+DEFAULT_CHARSET = "utf-8"
+## End Changes for Django 1.5
+
+## Changes for Django 1.5.4:
+LOGGING_CONFIG = None
+AUTH_USER_MODEL = 'auth.User'
+## End Changes for Django 1.5.4
+
+LOGIN_URL = "/login/"
+LOGOUT_URL = "/logout"
+LOGIN_REDIRECT_URL = "/"
+
+## Changes for Django 1.6:
+LOGGING = None
+
+## Changes for Django 1.7.1:
+ABSOLUTE_URL_OVERRIDES = {}
diff --git a/gramps/plugins/database/django_support/defaults/sqlite.db b/gramps/plugins/database/django_support/defaults/sqlite.db
new file mode 100644
index 0000000000000000000000000000000000000000..a9ac911f065f752dfb25be75e2916ff5df508451
GIT binary patch
literal 293888
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
N%rWbcAX@iQ{{@Gr__6>1

literal 0
HcmV?d00001

diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index 56f40f5ac..e380c9601 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -30,6 +30,9 @@ import re
 import base64
 import pickle
 import os
+import logging
+import shutil
+from django.db import transaction
 
 #------------------------------------------------------------------------
 #
@@ -53,13 +56,13 @@ from gramps.gen.db import (PERSON_KEY,
                     REPOSITORY_KEY,
                     NOTE_KEY)
 from gramps.gen.utils.id import create_id
-from django.db import transaction
+from gramps.gen.db.dbconst import *
 
 ## add this directory to sys path, so we can find django_support later:
-import sys
-import os
 sys.path.append(os.path.dirname(os.path.abspath(__file__)))
 
+_LOG = logging.getLogger(DBLOGNAME)
+
 class Environment(object):
     """
     Implements the Environment API.
@@ -2041,3 +2044,17 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def restore(self):
         pass
+
+    def write_version(self, directory):
+        """Write files for a newly created DB."""
+        versionpath = os.path.join(directory, str(DBBACKEND))
+        _LOG.debug("Write database backend file to 'djangodb'")
+        with open(versionpath, "w") as version_file:
+            version_file.write("djangodb")
+        # Write default_settings, sqlite.db
+        defaults = os.path.join(os.path.dirname(os.path.abspath(__file__)),
+                                "django_support", "defaults")
+        _LOG.debug("Copy defaults from: " + defaults)
+        for filename in os.listdir(defaults):
+            fullpath = os.path.abspath(os.path.join(defaults, filename))
+            shutil.copy2(fullpath, directory)

From 6da7f78cba91fd8aac69e3db9c6dc2b470e86ab3 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Thu, 14 May 2015 07:01:47 -0400
Subject: [PATCH 062/105] Hack to reset modules on subsequent uses of Django
 databases

---
 gramps/gen/dbstate.py | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py
index 04a8e683d..069934a13 100644
--- a/gramps/gen/dbstate.py
+++ b/gramps/gen/dbstate.py
@@ -22,6 +22,7 @@
 """
 Provide the database state class
 """
+import sys
 
 from .db import DbReadBase
 from .proxy.proxybase import ProxyDbBase
@@ -139,6 +140,34 @@ class DbState(Callback):
             pmgr.reg_plugins(USER_PLUGINS, self, None, load_on_reg=True)
             pdata = pmgr.get_plugin(id)
 
+        ### FIXME: Currently Django needs to reset modules
+        if pdata.id == "djangodb":
+            if self.modules_is_set():
+                self.reset_modules()
+            else:
+                self.save_modules()
+
         mod = pmgr.load_plugin(pdata)
         database = getattr(mod, pdata.databaseclass)
         return database()
+
+    ## FIXME:
+    ## Work-around for databases that need sys refresh (django):
+    def modules_is_set(self):
+        if hasattr(self, "_modules"):
+            return self._modules != None
+        else:
+            self._modules = None
+            return False
+
+    def reset_modules(self):
+        # First, clear out old modules:
+        for key in list(sys.modules.keys()):
+            del(sys.modules[key])
+        # Next, restore previous:
+        for key in self._modules:
+            sys.modules[key] = self._modules[key]
+
+    def save_modules(self):
+        self._modules = sys.modules.copy()
+

From 1118ce449c72ec2ae0b93b6b49be4bcab349e95a Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Thu, 14 May 2015 09:07:10 -0400
Subject: [PATCH 063/105] Reworked backend Cursors; don't emit changes when
 changing in batch mode

---
 gramps/plugins/database/__init__.py     |    0
 gramps/plugins/database/dbdjango.py     | 2036 -----------------------
 gramps/plugins/database/dictionarydb.py |  106 +-
 gramps/plugins/database/djangodb.py     |  103 +-
 4 files changed, 121 insertions(+), 2124 deletions(-)
 create mode 100644 gramps/plugins/database/__init__.py
 delete mode 100644 gramps/plugins/database/dbdjango.py

diff --git a/gramps/plugins/database/__init__.py b/gramps/plugins/database/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/gramps/plugins/database/dbdjango.py b/gramps/plugins/database/dbdjango.py
deleted file mode 100644
index adb28dc54..000000000
--- a/gramps/plugins/database/dbdjango.py
+++ /dev/null
@@ -1,2036 +0,0 @@
-# Gramps - a GTK+/GNOME based genealogy program
-#
-# Copyright (C) 2009         Douglas S. Blank <doug.blank@gmail.com>
-#
-# 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; either version 2 of the License, or
-# (at your option) any later version.
-#
-# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-
-""" Implements a Db interface """
-
-#------------------------------------------------------------------------
-#
-# Python Modules
-#
-#------------------------------------------------------------------------
-import sys
-import time
-import re
-import base64
-import pickle
-import os
-
-#------------------------------------------------------------------------
-#
-# Gramps Modules
-#
-#------------------------------------------------------------------------
-import gramps
-from gramps.gen.lib import (Person, Family, Event, Place, Repository, 
-                            Citation, Source, Note, MediaObject, Tag, 
-                            Researcher)
-from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn
-from gramps.gen.utils.callback import Callback
-from gramps.gen.updatecallback import UpdateCallback
-from gramps.gen.db import (PERSON_KEY,
-                    FAMILY_KEY,
-                    CITATION_KEY,
-                    SOURCE_KEY,
-                    EVENT_KEY,
-                    MEDIA_KEY,
-                    PLACE_KEY,
-                    REPOSITORY_KEY,
-                    NOTE_KEY)
-from gramps.gen.utils.id import create_id
-from django.db import transaction
-
-class Environment(object):
-    """
-    Implements the Environment API.
-    """
-    def __init__(self, db):
-        self.db = db
-
-    def txn_begin(self):
-        return DjangoTxn("DbDjango Transaction", self.db)
-
-class Table(object):
-    """
-    Implements Table interface.
-    """
-    def __init__(self, funcs):
-        self.funcs = funcs
-
-    def cursor(self):
-        """
-        Returns a Cursor for this Table.
-        """
-        return self.funcs["cursor_func"]()
-
-    def put(key, data, txn=None):
-        self[key] = data
-
-class Map(dict):
-    """
-    Implements the map API for person_map, etc.
-    
-    Takes a Table() as argument.
-    """
-    def __init__(self, tbl, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self.db = tbl
-
-class MetaCursor(object):
-    def __init__(self):
-        pass
-    def __enter__(self):
-        return self
-    def __iter__(self):
-        return self.__next__()
-    def __next__(self):
-        yield None
-    def __exit__(self, *args, **kwargs):
-        pass
-    def iter(self):
-        yield None
-    def first(self):
-        self._iter = self.__iter__()
-        return self.next()
-    def next(self):
-        try:
-            return next(self._iter)
-        except:
-            return None
-    def close(self):
-        pass
-
-class Cursor(object):
-    def __init__(self, model, func):
-        self.model = model
-        self.func = func
-    def __enter__(self):
-        return self
-    def __iter__(self):
-        return self.__next__()
-    def __next__(self):
-        for item in self.model.all():
-            yield (bytes(item.handle, "utf-8"), self.func(item.handle))
-    def __exit__(self, *args, **kwargs):
-        pass
-    def iter(self):
-        for item in self.model.all():
-            yield (bytes(item.handle, "utf-8"), self.func(item.handle))
-        yield None
-    def first(self):
-        self._iter = self.__iter__()
-        return self.next()
-    def next(self):
-        try:
-            return next(self._iter)
-        except:
-            return None
-    def close(self):
-        pass
-
-class Bookmarks(object):
-    def __init__(self):
-        self.handles = []
-    def get(self):
-        return self.handles
-    def append(self, handle):
-        self.handles.append(handle)
-
-class DjangoTxn(DbTxn):
-    def __init__(self, message, db, table=None):
-        DbTxn.__init__(self, message, db)
-        self.table = table
-
-    def get(self, key, default=None, txn=None, **kwargs):
-        """
-        Returns the data object associated with key
-        """
-        try:
-            return self.table.objects(handle=key)
-        except:
-            if txn and key in txn:
-                return txn[key]
-            else:
-                return None
-
-    def put(self, handle, new_data, txn):
-        """
-        """
-        txn[handle] = new_data
-
-    def commit(self):
-        pass
-
-class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
-    """
-    A Gramps Database Backend. This replicates the grampsdb functions.
-    """
-    # Set up dictionary for callback signal handler
-    # ---------------------------------------------
-    # 1. Signals for primary objects
-    __signals__ = dict((obj+'-'+op, signal)
-                       for obj in
-                       ['person', 'family', 'event', 'place',
-                        'source', 'citation', 'media', 'note', 'repository', 'tag']
-                       for op, signal in zip(
-                               ['add',   'update', 'delete', 'rebuild'],
-                               [(list,), (list,),  (list,),   None]
-                       )
-                   )
-    
-    # 2. Signals for long operations
-    __signals__.update(('long-op-'+op, signal) for op, signal in zip(
-        ['start',  'heartbeat', 'end'],
-        [(object,), None,       None]
-        ))
-
-    # 3. Special signal for change in home person
-    __signals__['home-person-changed'] = None
-
-    # 4. Signal for change in person group name, parameters are
-    __signals__['person-groupname-rebuild'] = (str, str)
-
-    def __init__(self, directory=None):
-        DbReadBase.__init__(self)
-        DbWriteBase.__init__(self)
-        Callback.__init__(self)
-        self._tables = {
-            'Person':
-                {
-                "handle_func": self.get_person_from_handle, 
-                "gramps_id_func": self.get_person_from_gramps_id,
-                "class_func": gramps.gen.lib.Person,
-                "cursor_func": self.get_person_cursor,
-                "handles_func": self.get_person_handles,
-                "iter_func": self.iter_people,
-                },
-            'Family':
-                {
-                "handle_func": self.get_family_from_handle, 
-                "gramps_id_func": self.get_family_from_gramps_id,
-                "class_func": gramps.gen.lib.Family,
-                "cursor_func": self.get_family_cursor,
-                "handles_func": self.get_family_handles,
-                "iter_func": self.iter_families,
-                },
-            'Source':
-                {
-                "handle_func": self.get_source_from_handle, 
-                "gramps_id_func": self.get_source_from_gramps_id,
-                "class_func": gramps.gen.lib.Source,
-                "cursor_func": self.get_source_cursor,
-                "handles_func": self.get_source_handles,
-                "iter_func": self.iter_sources,
-                },
-            'Citation':
-                {
-                "handle_func": self.get_citation_from_handle, 
-                "gramps_id_func": self.get_citation_from_gramps_id,
-                "class_func": gramps.gen.lib.Citation,
-                "cursor_func": self.get_citation_cursor,
-                "handles_func": self.get_citation_handles,
-                "iter_func": self.iter_citations,
-                },
-            'Event':
-                {
-                "handle_func": self.get_event_from_handle, 
-                "gramps_id_func": self.get_event_from_gramps_id,
-                "class_func": gramps.gen.lib.Event,
-                "cursor_func": self.get_event_cursor,
-                "handles_func": self.get_event_handles,
-                "iter_func": self.iter_events,
-                },
-            'Media':
-                {
-                "handle_func": self.get_object_from_handle, 
-                "gramps_id_func": self.get_object_from_gramps_id,
-                "class_func": gramps.gen.lib.MediaObject,
-                "cursor_func": self.get_media_cursor,
-                "handles_func": self.get_media_object_handles,
-                "iter_func": self.iter_media_objects,
-                },
-            'Place':
-                {
-                "handle_func": self.get_place_from_handle, 
-                "gramps_id_func": self.get_place_from_gramps_id,
-                "class_func": gramps.gen.lib.Place,
-                "cursor_func": self.get_place_cursor,
-                "handles_func": self.get_place_handles,
-                "iter_func": self.iter_places,
-                },
-            'Repository':
-                {
-                "handle_func": self.get_repository_from_handle, 
-                "gramps_id_func": self.get_repository_from_gramps_id,
-                "class_func": gramps.gen.lib.Repository,
-                "cursor_func": self.get_repository_cursor,
-                "handles_func": self.get_repository_handles,
-                "iter_func": self.iter_repositories,
-                },
-            'Note':
-                {
-                "handle_func": self.get_note_from_handle, 
-                "gramps_id_func": self.get_note_from_gramps_id,
-                "class_func": gramps.gen.lib.Note,
-                "cursor_func": self.get_note_cursor,
-                "handles_func": self.get_note_handles,
-                "iter_func": self.iter_notes,
-                },
-            'Tag':
-                {
-                "handle_func": self.get_tag_from_handle, 
-                "gramps_id_func": None,
-                "class_func": gramps.gen.lib.Tag,
-                "cursor_func": self.get_tag_cursor,
-                "handles_func": self.get_tag_handles,
-                "iter_func": self.iter_tags,
-                },
-            }
-        # skip GEDCOM cross-ref check for now:
-        self.set_feature("skip-check-xref", True)
-        self.readonly = False
-        self.db_is_open = True
-        self.name_formats = []
-        self.bookmarks = Bookmarks()
-        self.undo_callback = None
-        self.redo_callback = None
-        self.undo_history_callback = None
-        self.modified   = 0
-        self.txn = DjangoTxn("DbDjango Transaction", self)
-        self.transaction = None
-        # Import cache for gedcom import, uses transactions, and
-        # two step adding of objects.
-        self.import_cache = {}
-        self.use_import_cache = False
-        self.use_db_cache = True
-        self._directory = directory
-        if directory:
-            self.load(directory)
-
-    def load(self, directory, pulse_progress=None, mode=None):
-        self._directory = directory
-        from django.conf import settings
-        default_settings = {}
-        settings_file = os.path.join(directory, "default_settings.py")
-        with open(settings_file) as f:
-            code = compile(f.read(), settings_file, 'exec')
-            exec(code, globals(), default_settings)
-
-        class Module(object):
-            def __init__(self, dictionary):
-                self.dictionary = dictionary
-            def __getattr__(self, item):
-                return self.dictionary[item]
-
-        try:
-            settings.configure(Module(default_settings))
-        except RuntimeError:
-            # already configured; ignore
-            pass
-
-        import django
-        django.setup()
-
-        from django_support.libdjango import DjangoInterface
-        self.dji = DjangoInterface()
-        self.family_bookmarks = Bookmarks()
-        self.event_bookmarks = Bookmarks()
-        self.place_bookmarks = Bookmarks()
-        self.citation_bookmarks = Bookmarks()
-        self.source_bookmarks = Bookmarks()
-        self.repo_bookmarks = Bookmarks()
-        self.media_bookmarks = Bookmarks()
-        self.note_bookmarks = Bookmarks()
-        self.set_person_id_prefix('I%04d')
-        self.set_object_id_prefix('O%04d')
-        self.set_family_id_prefix('F%04d')
-        self.set_citation_id_prefix('C%04d')
-        self.set_source_id_prefix('S%04d')
-        self.set_place_id_prefix('P%04d')
-        self.set_event_id_prefix('E%04d')
-        self.set_repository_id_prefix('R%04d')
-        self.set_note_id_prefix('N%04d')
-        # ----------------------------------
-        self.id_trans  = DjangoTxn("ID Transaction", self, self.dji.Person)
-        self.fid_trans = DjangoTxn("FID Transaction", self, self.dji.Family)
-        self.pid_trans = DjangoTxn("PID Transaction", self, self.dji.Place)
-        self.cid_trans = DjangoTxn("CID Transaction", self, self.dji.Citation)
-        self.sid_trans = DjangoTxn("SID Transaction", self, self.dji.Source)
-        self.oid_trans = DjangoTxn("OID Transaction", self, self.dji.Media)
-        self.rid_trans = DjangoTxn("RID Transaction", self, self.dji.Repository)
-        self.nid_trans = DjangoTxn("NID Transaction", self, self.dji.Note)
-        self.eid_trans = DjangoTxn("EID Transaction", self, self.dji.Event)
-        self.cmap_index = 0
-        self.smap_index = 0
-        self.emap_index = 0
-        self.pmap_index = 0
-        self.fmap_index = 0
-        self.lmap_index = 0
-        self.omap_index = 0
-        self.rmap_index = 0
-        self.nmap_index = 0
-        self.env = Environment(self)
-        self.person_map = Map(Table(self._tables["Person"]))
-        self.family_map = Map(Table(self._tables["Family"]))
-        self.place_map  = Map(Table(self._tables["Place"]))
-        self.citation_map = Map(Table(self._tables["Citation"]))
-        self.source_map = Map(Table(self._tables["Source"]))
-        self.repository_map  = Map(Table(self._tables["Repository"]))
-        self.note_map = Map(Table(self._tables["Note"]))
-        self.media_map  = Map(Table(self._tables["Media"]))
-        self.event_map  = Map(Table(self._tables["Event"]))
-        self.tag_map  = Map(Table(self._tables["Tag"]))
-        self.metadata   = Map(Table({"cursor_func": lambda: MetaCursor()}))
-        self.name_group = {}
-        self.event_names = set()
-        self.individual_attributes = set()
-        self.family_attributes = set()
-        self.source_attributes = set()
-        self.child_ref_types = set()
-        self.family_rel_types = set()
-        self.event_role_names = set()
-        self.name_types = set()
-        self.origin_types = set()
-        self.repository_types = set()
-        self.note_types = set()
-        self.source_media_types = set()
-        self.url_types = set()
-        self.media_attributes = set()
-        self.place_types = set()
-
-    def prepare_import(self):
-        """
-        DbDjango does not commit data on gedcom import, but saves them
-        for later commit.
-        """
-        self.use_import_cache = True
-        self.import_cache = {}
-
-    @transaction.atomic
-    def commit_import(self):
-        """
-        Commits the items that were queued up during the last gedcom
-        import for two step adding.
-        """
-        # First we add the primary objects:
-        for key in list(self.import_cache.keys()):
-            obj = self.import_cache[key]
-            if isinstance(obj, Person):
-                self.dji.add_person(obj.serialize())
-            elif isinstance(obj, Family):
-                self.dji.add_family(obj.serialize())
-            elif isinstance(obj, Event):
-                self.dji.add_event(obj.serialize())
-            elif isinstance(obj, Place):
-                self.dji.add_place(obj.serialize())
-            elif isinstance(obj, Repository):
-                self.dji.add_repository(obj.serialize())
-            elif isinstance(obj, Citation):
-                self.dji.add_citation(obj.serialize())
-            elif isinstance(obj, Source):
-                self.dji.add_source(obj.serialize())
-            elif isinstance(obj, Note):
-                self.dji.add_note(obj.serialize())
-            elif isinstance(obj, MediaObject):
-                self.dji.add_media(obj.serialize())
-            elif isinstance(obj, Tag):
-                self.dji.add_tag(obj.serialize())
-        # Next we add the links:
-        for key in list(self.import_cache.keys()):
-            obj = self.import_cache[key]
-            if isinstance(obj, Person):
-                self.dji.add_person_detail(obj.serialize())
-            elif isinstance(obj, Family):
-                self.dji.add_family_detail(obj.serialize())
-            elif isinstance(obj, Event):
-                self.dji.add_event_detail(obj.serialize())
-            elif isinstance(obj, Place):
-                self.dji.add_place_detail(obj.serialize())
-            elif isinstance(obj, Repository):
-                self.dji.add_repository_detail(obj.serialize())
-            elif isinstance(obj, Citation):
-                self.dji.add_citation_detail(obj.serialize())
-            elif isinstance(obj, Source):
-                self.dji.add_source_detail(obj.serialize())
-            elif isinstance(obj, Note):
-                self.dji.add_note_detail(obj.serialize())
-            elif isinstance(obj, MediaObject):
-                self.dji.add_media_detail(obj.serialize())
-            elif isinstance(obj, Tag):
-                self.dji.add_tag_detail(obj.serialize())
-        self.use_import_cache = False
-        self.import_cache = {}
-        self.dji.update_publics()
-
-    def transaction_commit(self, txn):
-        pass
-
-    def request_rebuild(self):
-        # caches are ok, but let's compute public's
-        self.dji.update_publics()
-        self.emit('person-rebuild')
-        self.emit('family-rebuild')
-        self.emit('place-rebuild')
-        self.emit('source-rebuild')
-        self.emit('citation-rebuild')
-        self.emit('media-rebuild')
-        self.emit('event-rebuild')
-        self.emit('repository-rebuild')
-        self.emit('note-rebuild')
-        self.emit('tag-rebuild')
-
-    def get_undodb(self):
-        return None
-
-    def transaction_abort(self, txn):
-        pass
-
-    @staticmethod
-    def _validated_id_prefix(val, default):
-        if isinstance(val, str) and val:
-            try:
-                str_ = val % 1
-            except TypeError:           # missing conversion specifier
-                prefix_var = val + "%d"
-            except ValueError:          # incomplete format
-                prefix_var = default+"%04d"
-            else:
-                prefix_var = val        # OK as given
-        else:
-            prefix_var = default+"%04d" # not a string or empty string
-        return prefix_var
-
-    @staticmethod
-    def __id2user_format(id_pattern):
-        """
-        Return a method that accepts a Gramps ID and adjusts it to the users
-        format.
-        """
-        pattern_match = re.match(r"(.*)%[0 ](\d+)[diu]$", id_pattern)
-        if pattern_match:
-            str_prefix = pattern_match.group(1)
-            nr_width = pattern_match.group(2)
-            def closure_func(gramps_id):
-                if gramps_id and gramps_id.startswith(str_prefix):
-                    id_number = gramps_id[len(str_prefix):]
-                    if id_number.isdigit():
-                        id_value = int(id_number, 10)
-                        #if len(str(id_value)) > nr_width:
-                        #    # The ID to be imported is too large to fit in the
-                        #    # users format. For now just create a new ID,
-                        #    # because that is also what happens with IDs that
-                        #    # are identical to IDs already in the database. If
-                        #    # the problem of colliding import and already
-                        #    # present IDs is solved the code here also needs
-                        #    # some solution.
-                        #    gramps_id = id_pattern % 1
-                        #else:
-                        gramps_id = id_pattern % id_value
-                return gramps_id
-        else:
-            def closure_func(gramps_id):
-                return gramps_id
-        return closure_func
-
-    def set_person_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Person ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as I%d or I%04d.
-        """
-        self.person_prefix = self._validated_id_prefix(val, "I")
-        self.id2user_format = self.__id2user_format(self.person_prefix)
-
-    def set_citation_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Citation ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as C%d or C%04d.
-        """
-        self.citation_prefix = self._validated_id_prefix(val, "C")
-        self.cid2user_format = self.__id2user_format(self.citation_prefix)
-            
-    def set_source_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Source ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as S%d or S%04d.
-        """
-        self.source_prefix = self._validated_id_prefix(val, "S")
-        self.sid2user_format = self.__id2user_format(self.source_prefix)
-            
-    def set_object_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS MediaObject ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as O%d or O%04d.
-        """
-        self.mediaobject_prefix = self._validated_id_prefix(val, "O")
-        self.oid2user_format = self.__id2user_format(self.mediaobject_prefix)
-
-    def set_place_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Place ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as P%d or P%04d.
-        """
-        self.place_prefix = self._validated_id_prefix(val, "P")
-        self.pid2user_format = self.__id2user_format(self.place_prefix)
-
-    def set_family_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Family ID values. The string is
-        expected to be in the form of a simple text string, or in a format
-        that contains a C/Python style format string using %d, such as F%d
-        or F%04d.
-        """
-        self.family_prefix = self._validated_id_prefix(val, "F")
-        self.fid2user_format = self.__id2user_format(self.family_prefix)
-
-    def set_event_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Event ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as E%d or E%04d.
-        """
-        self.event_prefix = self._validated_id_prefix(val, "E")
-        self.eid2user_format = self.__id2user_format(self.event_prefix)
-
-    def set_repository_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Repository ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as R%d or R%04d.
-        """
-        self.repository_prefix = self._validated_id_prefix(val, "R")
-        self.rid2user_format = self.__id2user_format(self.repository_prefix)
-
-    def set_note_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Note ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as N%d or N%04d.
-        """
-        self.note_prefix = self._validated_id_prefix(val, "N")
-        self.nid2user_format = self.__id2user_format(self.note_prefix)
-
-    def __find_next_gramps_id(self, prefix, map_index, trans):
-        """
-        Helper function for find_next_<object>_gramps_id methods
-        """
-        index = prefix % map_index
-        while trans.get(str(index), txn=self.txn) is not None:
-            map_index += 1
-            index = prefix % map_index
-        map_index += 1
-        return (map_index, index)
-        
-    def find_next_person_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Person object based off the 
-        person ID prefix.
-        """
-        self.pmap_index, gid = self.__find_next_gramps_id(self.person_prefix,
-                                          self.pmap_index, self.id_trans)
-        return gid
-
-    def find_next_place_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Place object based off the 
-        place ID prefix.
-        """
-        self.lmap_index, gid = self.__find_next_gramps_id(self.place_prefix,
-                                          self.lmap_index, self.pid_trans)
-        return gid
-
-    def find_next_event_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Event object based off the 
-        event ID prefix.
-        """
-        self.emap_index, gid = self.__find_next_gramps_id(self.event_prefix,
-                                          self.emap_index, self.eid_trans)
-        return gid
-
-    def find_next_object_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a MediaObject object based
-        off the media object ID prefix.
-        """
-        self.omap_index, gid = self.__find_next_gramps_id(self.mediaobject_prefix,
-                                          self.omap_index, self.oid_trans)
-        return gid
-
-    def find_next_citation_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Citation object based off the 
-        citation ID prefix.
-        """
-        self.cmap_index, gid = self.__find_next_gramps_id(self.citation_prefix,
-                                          self.cmap_index, self.cid_trans)
-        return gid
-
-    def find_next_source_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Source object based off the 
-        source ID prefix.
-        """
-        self.smap_index, gid = self.__find_next_gramps_id(self.source_prefix,
-                                          self.smap_index, self.sid_trans)
-        return gid
-
-    def find_next_family_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Family object based off the 
-        family ID prefix.
-        """
-        self.fmap_index, gid = self.__find_next_gramps_id(self.family_prefix,
-                                          self.fmap_index, self.fid_trans)
-        return gid
-
-    def find_next_repository_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Respository object based 
-        off the repository ID prefix.
-        """
-        self.rmap_index, gid = self.__find_next_gramps_id(self.repository_prefix,
-                                          self.rmap_index, self.rid_trans)
-        return gid
-
-    def find_next_note_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Note object based off the 
-        note ID prefix.
-        """
-        self.nmap_index, gid = self.__find_next_gramps_id(self.note_prefix,
-                                          self.nmap_index, self.nid_trans)
-        return gid
-
-    def get_mediapath(self):
-        return None
-
-    def get_name_group_keys(self):
-        return []
-
-    def get_name_group_mapping(self, key):
-        return None
-
-    def get_researcher(self):
-        obj = Researcher()
-        return obj
-
-    def get_tag_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Tag.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Tag.all()]
-
-    def get_person_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Person.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Person.all()]
-
-    def get_family_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Family.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Family.all()]
-
-    def get_event_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Event.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Event.all()]
-
-    def get_citation_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Citation.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Citation.all()]
-
-    def get_source_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Source.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Source.all()]
-
-    def get_place_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Place.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Place.all()]
-
-    def get_repository_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Repository.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Repository.all()]
-
-    def get_media_object_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Media.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Media.all()]
-
-    def get_note_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Note.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Note.all()]
-
-    def get_media_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            media = self.dji.Media.get(handle=handle)
-        except:
-            return None
-        return self.make_media(media)
-
-    def get_event_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            event = self.dji.Event.get(handle=handle)
-        except:
-            return None
-        return self.make_event(event)
-
-    def get_family_from_handle(self, handle): 
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            family = self.dji.Family.get(handle=handle)
-        except:
-            return None
-        return self.make_family(family)
-
-    def get_repository_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            repository = self.dji.Repository.get(handle=handle)
-        except:
-            return None
-        return self.make_repository(repository)
-
-    def get_person_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            person = self.dji.Person.get(handle=handle)
-        except:
-            return None
-        return self.make_person(person)
-
-    def get_tag_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            tag = self.dji.Tag.get(handle=handle)
-        except:
-            return None
-        return self.make_tag(tag)
-
-    def make_repository(self, repository):
-        if self.use_db_cache and repository.cache:
-            data = repository.from_cache()
-        else:
-            data = self.dji.get_repository(repository)
-        return Repository.create(data)
-
-    def make_citation(self, citation):
-        if self.use_db_cache and citation.cache:
-            data = citation.from_cache()
-        else:
-            data = self.dji.get_citation(citation)
-        return Citation.create(data)
-
-    def make_source(self, source):
-        if self.use_db_cache and source.cache:
-            data = source.from_cache()
-        else:
-            data = self.dji.get_source(source)
-        return Source.create(data)
-
-    def make_family(self, family):
-        if self.use_db_cache and family.cache:
-            data = family.from_cache()
-        else:
-            data = self.dji.get_family(family)
-        return Family.create(data)
-
-    def make_person(self, person):
-        if self.use_db_cache and person.cache:
-            data = person.from_cache()
-        else:
-            data = self.dji.get_person(person)
-        return Person.create(data)
-
-    def make_event(self, event):
-        if self.use_db_cache and event.cache:
-            data = event.from_cache()
-        else:
-            data = self.dji.get_event(event)
-        return Event.create(data)
-
-    def make_note(self, note):
-        if self.use_db_cache and note.cache:
-            data = note.from_cache()
-        else:
-            data = self.dji.get_note(note)
-        return Note.create(data)
-
-    def make_tag(self, tag):
-        data = self.dji.get_tag(tag)
-        return Tag.create(data)
-
-    def make_place(self, place):
-        if self.use_db_cache and place.cache:
-            data = place.from_cache()
-        else:
-            data = self.dji.get_place(place)
-        return Place.create(data)
-
-    def make_media(self, media):
-        if self.use_db_cache and media.cache:
-            data = media.from_cache()
-        else:
-            data = self.dji.get_media(media)
-        return MediaObject.create(data)
-
-    def get_place_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            place = self.dji.Place.get(handle=handle)
-        except:
-            return None
-        return self.make_place(place)
-
-    def get_citation_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            citation = self.dji.Citation.get(handle=handle)
-        except:
-            return None
-        return self.make_citation(citation)
-
-    def get_source_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            source = self.dji.Source.get(handle=handle)
-        except:
-            return None
-        return self.make_source(source)
-
-    def get_note_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            note = self.dji.Note.get(handle=handle)
-        except:
-            return None
-        return self.make_note(note)
-
-    def get_object_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            media = self.dji.Media.get(handle=handle)
-        except:
-            return None
-        return self.make_media(media)
-
-    def get_default_person(self):
-        people = self.dji.Person.all()
-        if people.count() > 0:
-            return self.make_person(people[0])
-        return None
-
-    def iter_people(self):
-        return (self.get_person_from_handle(person.handle) 
-                for person in self.dji.Person.all())
-
-    def iter_person_handles(self):
-        return (person.handle for person in self.dji.Person.all())
-
-    def iter_families(self):
-        return (self.get_family_from_handle(family.handle) 
-                for family in self.dji.Family.all())
-
-    def iter_family_handles(self):
-        return (family.handle for family in self.dji.Family.all())
-
-    def iter_notes(self):
-        return (self.get_note_from_handle(note.handle) 
-                for note in self.dji.Note.all())
-
-    def iter_note_handles(self):
-        return (note.handle for note in self.dji.Note.all())
-
-    def iter_events(self):
-        return (self.get_event_from_handle(event.handle) 
-                for event in self.dji.Event.all())
-
-    def iter_event_handles(self):
-        return (event.handle for event in self.dji.Event.all())
-
-    def iter_places(self):
-        return (self.get_place_from_handle(place.handle) 
-                for place in self.dji.Place.all())
-
-    def iter_place_handles(self):
-        return (place.handle for place in self.dji.Place.all())
-
-    def iter_repositories(self):
-        return (self.get_repository_from_handle(repository.handle) 
-                for repository in self.dji.Repository.all())
-
-    def iter_repository_handles(self):
-        return (repository.handle for repository in self.dji.Repository.all())
-
-    def iter_sources(self):
-        return (self.get_source_from_handle(source.handle) 
-                for source in self.dji.Source.all())
-
-    def iter_source_handles(self):
-        return (source.handle for source in self.dji.Source.all())
-
-    def iter_citations(self):
-        return (self.get_citation_from_handle(citation.handle) 
-                for citation in self.dji.Citation.all())
-
-    def iter_citation_handles(self):
-        return (citation.handle for citation in self.dji.Citation.all())
-
-    def iter_tags(self):
-        return (self.get_tag_from_handle(tag.handle) 
-                for tag in self.dji.Tag.all())
-
-    def iter_tag_handles(self):
-        return (tag.handle for tag in self.dji.Tag.all())
-
-    def iter_media_objects(self):
-        return (self.get_media_from_handle(media.handle) 
-                for media in self.dji.Media.all())
-
-    def get_tag_from_name(self, name):
-        try:
-            tag = self.dji.Tag.filter(name=name)
-            return self.make_tag(tag[0])
-        except:
-            return None
-
-    def get_person_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        match_list = self.dji.Person.filter(gramps_id=gramps_id)
-        if match_list.count() > 0:
-            return self.make_person(match_list[0])
-        else:
-            return None
-
-    def get_family_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        try:
-            family = self.dji.Family.get(gramps_id=gramps_id)
-        except:
-            return None
-        return self.make_family(family)
-
-    def get_source_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        match_list = self.dji.Source.filter(gramps_id=gramps_id)
-        if match_list.count() > 0:
-            return self.make_source(match_list[0])
-        else:
-            return None
-
-    def get_citation_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        match_list = self.dji.Citation.filter(gramps_id=gramps_id)
-        if match_list.count() > 0:
-            return self.make_citation(match_list[0])
-        else:
-            return None
-
-    def get_event_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        match_list = self.dji.Event.filter(gramps_id=gramps_id)
-        if match_list.count() > 0:
-            return self.make_event(match_list[0])
-        else:
-            return None
-
-    def get_object_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        match_list = self.dji.Media.filter(gramps_id=gramps_id)
-        if match_list.count() > 0:
-            return self.make_media(match_list[0])
-        else:
-            return None
-
-    def get_place_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        match_list = self.dji.Place.filter(gramps_id=gramps_id)
-        if match_list.count() > 0:
-            return self.make_place(match_list[0])
-        else:
-            return None
-
-    def get_repository_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        match_list = self.dji.Repsoitory.filter(gramps_id=gramps_id)
-        if match_list.count() > 0:
-            return self.make_repository(match_list[0])
-        else:
-            return None
-
-    def get_note_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        match_list = self.dji.Note.filter(gramps_id=gramps_id)
-        if match_list.count() > 0:
-            return self.make_note(match_list[0])
-        else:
-            return None
-
-    def get_number_of_people(self):
-        return self.dji.Person.count()
-
-    def get_number_of_events(self):
-        return self.dji.Event.count()
-
-    def get_number_of_places(self):
-        return self.dji.Place.count()
-
-    def get_number_of_tags(self):
-        return self.dji.Tag.count()
-
-    def get_number_of_families(self):
-        return self.dji.Family.count()
-
-    def get_number_of_notes(self):
-        return self.dji.Note.count()
-
-    def get_number_of_citations(self):
-        return self.dji.Citation.count()
-
-    def get_number_of_sources(self):
-        return self.dji.Source.count()
-
-    def get_number_of_media_objects(self):
-        return self.dji.Media.count()
-
-    def get_number_of_repositories(self):
-        return self.dji.Repository.count()
-
-    def get_place_cursor(self):
-        return Cursor(self.dji.Place, self.get_raw_place_data)
-
-    def get_person_cursor(self):
-        return Cursor(self.dji.Person, self.get_raw_person_data)
-
-    def get_family_cursor(self):
-        return Cursor(self.dji.Family, self.get_raw_family_data)
-
-    def get_event_cursor(self):
-        return Cursor(self.dji.Event, self.get_raw_event_data)
-
-    def get_citation_cursor(self):
-        return Cursor(self.dji.Citation, self.get_raw_citation_data)
-
-    def get_source_cursor(self):
-        return Cursor(self.dji.Source, self.get_raw_source_data)
-
-    def get_note_cursor(self):
-        return Cursor(self.dji.Note, self.get_raw_note_data)
-
-    def get_tag_cursor(self):
-        return Cursor(self.dji.Tag, self.get_raw_tag_data)
-
-    def get_repository_cursor(self):
-        return Cursor(self.dji.Repository, self.get_raw_repository_data)
-
-    def get_media_cursor(self):
-        return Cursor(self.dji.Media, self.get_raw_object_data)
-
-    def has_gramps_id(self, obj_key, gramps_id):
-        key2table = {
-            PERSON_KEY:     self.dji.Person, 
-            FAMILY_KEY:     self.dji.Family, 
-            SOURCE_KEY:     self.dji.Source, 
-            CITATION_KEY:   self.dji.Citation, 
-            EVENT_KEY:      self.dji.Event, 
-            MEDIA_KEY:      self.dji.Media, 
-            PLACE_KEY:      self.dji.Place, 
-            REPOSITORY_KEY: self.dji.Repository, 
-            NOTE_KEY:       self.dji.Note, 
-            }
-        table = key2table[obj_key]
-        return table.filter(gramps_id=gramps_id).count() > 0
-
-    def has_person_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Person.filter(handle=handle).count() == 1
-
-    def has_family_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Family.filter(handle=handle).count() == 1
-
-    def has_citation_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Citation.filter(handle=handle).count() == 1
-
-    def has_source_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Source.filter(handle=handle).count() == 1
-
-    def has_repository_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Repository.filter(handle=handle).count() == 1
-
-    def has_note_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Note.filter(handle=handle).count() == 1
-
-    def has_place_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Place.filter(handle=handle).count() == 1
-
-    def has_event_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Event.filter(handle=handle).count() == 1
-
-    def has_tag_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Tag.filter(handle=handle).count() == 1
-
-    def has_object_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Media.filter(handle=handle).count() == 1
-
-    def has_name_group_key(self, key):
-        # FIXME:
-        return False
-
-    def set_name_group_mapping(self, key, value):
-        # FIXME:
-        pass
-
-    def set_default_person_handle(self, handle):
-        pass
-
-    def set_mediapath(self, mediapath):
-        pass
-
-    def get_raw_person_data(self, handle):
-        try:
-            return self.dji.get_person(self.dji.Person.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_family_data(self, handle):
-        try:
-            return self.dji.get_family(self.dji.Family.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_citation_data(self, handle):
-        try:
-            return self.dji.get_citation(self.dji.Citation.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_source_data(self, handle):
-        try:
-            return self.dji.get_source(self.dji.Source.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_repository_data(self, handle):
-        try:
-            return self.dji.get_repository(self.dji.Repository.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_note_data(self, handle):
-        try:
-            return self.dji.get_note(self.dji.Note.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_place_data(self, handle):
-        try:
-            return self.dji.get_place(self.dji.Place.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_object_data(self, handle):
-        try:
-            return self.dji.get_media(self.dji.Media.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_tag_data(self, handle):
-        try:
-            return self.dji.get_tag(self.dji.Tag.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_event_data(self, handle):
-        try:
-            return self.dji.get_event(self.dji.Event.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def add_person(self, person, trans, set_gid=True):
-        if not person.handle:
-            person.handle = create_id()
-        if not person.gramps_id and set_gid:
-            person.gramps_id = self.find_next_person_gramps_id()
-        self.commit_person(person, trans)
-        self.emit("person-add", ([person.handle],))
-        return person.handle
-
-    def add_family(self, family, trans, set_gid=True):
-        if not family.handle:
-            family.handle = create_id()
-        if not family.gramps_id and set_gid:
-            family.gramps_id = self.find_next_family_gramps_id()
-        self.commit_family(family, trans)
-        self.emit("family-add", ([family.handle],))
-        return family.handle
-
-    def add_citation(self, citation, trans, set_gid=True):
-        if not citation.handle:
-            citation.handle = create_id()
-        if not citation.gramps_id and set_gid:
-            citation.gramps_id = self.find_next_citation_gramps_id()
-        self.commit_citation(citation, trans)
-        self.emit("citation-add", ([citation.handle],))
-        return citation.handle
-
-    def add_source(self, source, trans, set_gid=True):
-        if not source.handle:
-            source.handle = create_id()
-        if not source.gramps_id and set_gid:
-            source.gramps_id = self.find_next_source_gramps_id()
-        self.commit_source(source, trans)
-        self.emit("source-add", ([source.handle],))
-        return source.handle
-
-    def add_repository(self, repository, trans, set_gid=True):
-        if not repository.handle:
-            repository.handle = create_id()
-        if not repository.gramps_id and set_gid:
-            repository.gramps_id = self.find_next_repository_gramps_id()
-        self.commit_repository(repository, trans)
-        self.emit("repository-add", ([repository.handle],))
-        return repository.handle
-
-    def add_note(self, note, trans, set_gid=True):
-        if not note.handle:
-            note.handle = create_id()
-        if not note.gramps_id and set_gid:
-            note.gramps_id = self.find_next_note_gramps_id()
-        self.commit_note(note, trans)
-        self.emit("note-add", ([note.handle],))
-        return note.handle
-
-    def add_place(self, place, trans, set_gid=True):
-        if not place.handle:
-            place.handle = create_id()
-        if not place.gramps_id and set_gid:
-            place.gramps_id = self.find_next_place_gramps_id()
-        self.commit_place(place, trans)
-        return place.handle
-
-    def add_event(self, event, trans, set_gid=True):
-        if not event.handle:
-            event.handle = create_id()
-        if not event.gramps_id and set_gid:
-            event.gramps_id = self.find_next_event_gramps_id()
-        self.commit_event(event, trans)
-        return event.handle
-
-    def add_tag(self, tag, trans):
-        if not tag.handle:
-            tag.handle = create_id()
-        self.commit_event(tag, trans)
-        return tag.handle
-
-    def add_object(self, obj, transaction, set_gid=True):
-        """
-        Add a MediaObject to the database, assigning internal IDs if they have
-        not already been defined.
-        
-        If not set_gid, then gramps_id is not set.
-        """
-        if not obj.handle:
-            obj.handle = create_id()
-        if not obj.gramps_id and set_gid:
-            obj.gramps_id = self.find_next_object_gramps_id()
-        self.commit_media_object(obj, transaction)
-        return obj.handle
-
-    def commit_person(self, person, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[person.handle] = person
-        else:
-            raw = person.serialize()
-            items = self.dji.Person.filter(handle=person.handle)
-            if items.count() > 0:
-                # Hack, for the moment: delete and re-add
-                items[0].delete()
-            self.dji.add_person(person.serialize())
-            self.dji.add_person_detail(person.serialize())
-            if items.count() > 0:
-                self.emit("person-update", ([person.handle],))
-            else:
-                self.emit("person-add", ([person.handle],))
-
-    def commit_family(self, family, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[family.handle] = family
-        else:
-            raw = family.serialize()
-            items = self.dji.Family.filter(handle=family.handle)
-            if items.count() > 0:
-                items[0].delete()
-            self.dji.add_family(family.serialize())
-            self.dji.add_family_detail(family.serialize())
-            if items.count() > 0:
-                self.emit("family-update", ([family.handle],))
-            else:
-                self.emit("family-add", ([family.handle],))
-
-    def commit_citation(self, citation, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[citation.handle] = citation
-        else:
-            raw = citation.serialize()
-            items = self.dji.Citation.filter(handle=citation.handle)
-            if items.count() > 0:
-                items[0].delete()
-            self.dji.add_citation(citation.serialize())
-            self.dji.add_citation_detail(citation.serialize())
-            if items.count() > 0:
-                self.emit("citation-update", ([citation.handle],))
-            else:
-                self.emit("citation-add", ([citation.handle],))
-
-    def commit_source(self, source, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[source.handle] = source
-        else:
-            raw = source.serialize()
-            items = self.dji.Source.filter(handle=source.handle)
-            if items.count() > 0:
-                items[0].delete()
-            self.dji.add_source(source.serialize())
-            self.dji.add_source_detail(source.serialize())
-            if items.count() > 0:
-                self.emit("source-update", ([source.handle],))
-            else:
-                self.emit("source-add", ([source.handle],))
-
-    def commit_repository(self, repository, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[repository.handle] = repository
-        else:
-            raw = repository.serialize()
-            items = self.dji.Repository.filter(handle=repository.handle)
-            if items.count() > 0:
-                items[0].delete()
-            self.dji.add_repository(repository.serialize())
-            self.dji.add_repository_detail(repository.serialize())
-            if items.count() > 0:
-                self.emit("repository-update", ([repository.handle],))
-            else:
-                self.emit("repository-add", ([repository.handle],))
-
-    def commit_note(self, note, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[note.handle] = note
-        else:
-            raw = note.serialize()
-            items = self.dji.Note.filter(handle=note.handle)
-            if items.count() > 0:
-                items[0].delete()
-            self.dji.add_note(note.serialize())
-            self.dji.add_note_detail(note.serialize())
-            if items.count() > 0:
-                self.emit("note-update", ([note.handle],))
-            else:
-                self.emit("note-add", ([note.handle],))
-
-    def commit_place(self, place, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[place.handle] = place
-        else:
-            raw = place.serialize()
-            items = self.dji.Place.filter(handle=place.handle)
-            if items.count() > 0:
-                items[0].delete()
-            self.dji.add_place(place.serialize())
-            self.dji.add_place_detail(place.serialize())
-            if items.count() > 0:
-                self.emit("place-update", ([place.handle],))
-            else:
-                self.emit("place-add", ([place.handle],))
-
-    def commit_event(self, event, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[event.handle] = event
-        else:
-            raw = event.serialize()
-            items = self.dji.Event.filter(handle=event.handle)
-            if items.count() > 0:
-                items[0].delete()
-            self.dji.add_event(event.serialize())
-            self.dji.add_event_detail(event.serialize())
-            if items.count() > 0:
-                self.emit("event-update", ([event.handle],))
-            else:
-                self.emit("event-add", ([event.handle],))
-
-    def commit_tag(self, tag, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[tag.handle] = tag
-        else:
-            raw = tag.serialize()
-            items = self.dji.Tag.filter(handle=tag.handle)
-            if items.count() > 0:
-                items[0].delete()
-            self.dji.add_tag(tag.serialize())
-            self.dji.add_tag_detail(tag.serialize())
-            if items.count() > 0:
-                self.emit("tag-update", ([tag.handle],))
-            else:
-                self.emit("tag-add", ([tag.handle],))
-
-    def commit_media_object(self, media, transaction, change_time=None):
-        """
-        Commit the specified MediaObject to the database, storing the changes
-        as part of the transaction.
-        """
-        if self.use_import_cache:
-            self.import_cache[obj.handle] = media
-        else:
-            raw = media.serialize()
-            items = self.dji.Media.filter(handle=media.handle)
-            if items.count() > 0:
-                items[0].delete()
-            self.dji.add_media(media.serialize())
-            self.dji.add_media_detail(media.serialize())
-            if items.count() > 0:
-                self.emit("media-update", ([media.handle],))
-            else:
-                self.emit("media-add", ([media.handle],))
-
-    def get_gramps_ids(self, obj_key):
-        key2table = {
-            PERSON_KEY:     self.id_trans, 
-            FAMILY_KEY:     self.fid_trans, 
-            CITATION_KEY:   self.cid_trans, 
-            SOURCE_KEY:     self.sid_trans, 
-            EVENT_KEY:      self.eid_trans, 
-            MEDIA_KEY:      self.oid_trans, 
-            PLACE_KEY:      self.pid_trans, 
-            REPOSITORY_KEY: self.rid_trans, 
-            NOTE_KEY:       self.nid_trans, 
-            }
-
-        table = key2table[obj_key]
-        return list(table.keys())
-
-    def transaction_begin(self, transaction):
-        return 
-
-    def set_researcher(self, owner):
-        pass
-
-    def copy_from_db(self, db):
-        """
-        A (possibily) implementation-specific method to get data from
-        db into this database.
-        """
-        # First we add the primary objects:
-        for key in db._tables.keys():
-            cursor = db._tables[key]["cursor_func"]
-            for (handle, data) in cursor():
-                if key == "Person":
-                    self.dji.add_person(data)
-                elif key == "Family":
-                    self.dji.add_family(data)
-                elif key == "Event":
-                    self.dji.add_event(data)
-                elif key == "Place":
-                    self.dji.add_place(data)
-                elif key == "Repository":
-                    self.dji.add_repository(data)
-                elif key == "Citation":
-                    self.dji.add_citation(data)
-                elif key == "Source":
-                    self.dji.add_source(data)
-                elif key == "Note":
-                    self.dji.add_note(data)
-                elif key == "Media":
-                    self.dji.add_media(data)
-                elif key == "Tag":
-                    self.dji.add_tag(data)
-        for key in db._tables.keys():
-            cursor = db._tables[key]["cursor_func"]
-            for (handle, data) in cursor():
-                if key == "Person":
-                    self.dji.add_person_detail(data)
-                elif key == "Family":
-                    self.dji.add_family_detail(data)
-                elif key == "Event":
-                    self.dji.add_event_detail(data)
-                elif key == "Place":
-                    self.dji.add_place_detail(data)
-                elif key == "Repository":
-                    self.dji.add_repository_detail(data)
-                elif key == "Citation":
-                    self.dji.add_citation_detail(data)
-                elif key == "Source":
-                    self.dji.add_source_detail(data)
-                elif key == "Note":
-                    self.dji.add_note_detail(data)
-                elif key == "Media":
-                    self.dji.add_media_detail(data)
-                elif key == "Tag":
-                    self.dji.add_tag_detail(data)
-            # Next we add the links:
-        self.dji.update_publics()
-
-    def get_from_name_and_handle(self, table_name, handle):
-        """
-        Returns a gen.lib object (or None) given table_name and
-        handle.
-
-        Examples:
-
-        >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008")
-        >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23")
-        """
-        if table_name in self._tables:
-            return self._tables[table_name]["handle_func"](handle)
-        return None
-
-    def is_empty(self):
-        """
-        Is the database empty?
-        """
-        return (self.get_number_of_people() == 0 and
-                self.get_number_of_events() == 0 and
-                self.get_number_of_places() == 0 and
-                self.get_number_of_tags() == 0 and
-                self.get_number_of_families() == 0 and
-                self.get_number_of_notes() == 0 and
-                self.get_number_of_citations() == 0 and
-                self.get_number_of_sources() == 0 and
-                self.get_number_of_media_objects() == 0 and
-                self.get_number_of_repositories() == 0)
-                
-    __callback_map = {}
-
-    def set_prefixes(self, person, media, family, source, citation,
-                     place, event, repository, note):
-        self.set_person_id_prefix(person)
-        self.set_object_id_prefix(media)
-        self.set_family_id_prefix(family)
-        self.set_source_id_prefix(source)
-        self.set_citation_id_prefix(citation)
-        self.set_place_id_prefix(place)
-        self.set_event_id_prefix(event)
-        self.set_repository_id_prefix(repository)
-        self.set_note_id_prefix(note)
-
-    def has_changed(self):
-        return False
-
-    def find_backlink_handles(self, handle, include_classes=None):
-        ## FIXME: figure out how to get objects that refer
-        ## to this handle
-        return []
-
-    def get_note_bookmarks(self):
-        return self.note_bookmarks
-
-    def get_media_bookmarks(self):
-        return self.media_bookmarks
-
-    def get_repo_bookmarks(self):
-        return self.repo_bookmarks
-
-    def get_citation_bookmarks(self):
-        return self.citation_bookmarks
-
-    def get_source_bookmarks(self):
-        return self.source_bookmarks
-
-    def get_place_bookmarks(self):
-        return self.place_bookmarks
-
-    def get_event_bookmarks(self):
-        return self.event_bookmarks
-
-    def get_bookmarks(self):
-        return self.bookmarks
-
-    def get_family_bookmarks(self):
-        return self.family_bookmarks
-
-    def get_save_path(self):
-        return "/tmp/"
-
-    ## Get types:
-    def get_event_attribute_types(self):
-        """
-        Return a list of all Attribute types assocated with Event instances
-        in the database.
-        """
-        return list(self.event_attributes)
-
-    def get_event_types(self):
-        """
-        Return a list of all event types in the database.
-        """
-        return list(self.event_names)
-
-    def get_person_event_types(self):
-        """
-        Deprecated:  Use get_event_types
-        """
-        return list(self.event_names)
-
-    def get_person_attribute_types(self):
-        """
-        Return a list of all Attribute types assocated with Person instances 
-        in the database.
-        """
-        return list(self.individual_attributes)
-
-    def get_family_attribute_types(self):
-        """
-        Return a list of all Attribute types assocated with Family instances 
-        in the database.
-        """
-        return list(self.family_attributes)
-
-    def get_family_event_types(self):
-        """
-        Deprecated:  Use get_event_types
-        """
-        return list(self.event_names)
-
-    def get_media_attribute_types(self):
-        """
-        Return a list of all Attribute types assocated with Media and MediaRef 
-        instances in the database.
-        """
-        return list(self.media_attributes)
-
-    def get_family_relation_types(self):
-        """
-        Return a list of all relationship types assocated with Family
-        instances in the database.
-        """
-        return list(self.family_rel_types)
-
-    def get_child_reference_types(self):
-        """
-        Return a list of all child reference types assocated with Family
-        instances in the database.
-        """
-        return list(self.child_ref_types)
-
-    def get_event_roles(self):
-        """
-        Return a list of all custom event role names assocated with Event
-        instances in the database.
-        """
-        return list(self.event_role_names)
-
-    def get_name_types(self):
-        """
-        Return a list of all custom names types assocated with Person
-        instances in the database.
-        """
-        return list(self.name_types)
-
-    def get_origin_types(self):
-        """
-        Return a list of all custom origin types assocated with Person/Surname
-        instances in the database.
-        """
-        return list(self.origin_types)
-
-    def get_repository_types(self):
-        """
-        Return a list of all custom repository types assocated with Repository 
-        instances in the database.
-        """
-        return list(self.repository_types)
-
-    def get_note_types(self):
-        """
-        Return a list of all custom note types assocated with Note instances 
-        in the database.
-        """
-        return list(self.note_types)
-
-    def get_source_attribute_types(self):
-        """
-        Return a list of all Attribute types assocated with Source/Citation
-        instances in the database.
-        """
-        return list(self.source_attributes)
-
-    def get_source_media_types(self):
-        """
-        Return a list of all custom source media types assocated with Source 
-        instances in the database.
-        """
-        return list(self.source_media_types)
-
-    def get_url_types(self):
-        """
-        Return a list of all custom names types assocated with Url instances 
-        in the database.
-        """
-        return list(self.url_types)
-
-    def get_place_types(self):
-        """
-        Return a list of all custom place types assocated with Place instances
-        in the database.
-        """
-        return list(self.place_types)
-
-    def get_default_handle(self):
-        people = self.dji.Person.all()
-        if people.count() > 0:
-            return people[0].handle
-        return None
-
-    def close(self):
-        pass
-
-    def get_surname_list(self):
-        return []
-
-    def is_open(self):
-        return True
-
-    def get_table_names(self):
-        """Return a list of valid table names."""
-        return list(self._tables.keys())
-
-    def find_initial_person(self):
-        return self.get_default_person()
-
-    # Removals:
-    def remove_person(self, handle, txn):
-        self.dji.Person.filter(handle=handle)[0].delete()
-        self.emit("person-delete", ([handle],))
-
-    def remove_source(self, handle, transaction):
-        self.dji.Source.filter(handle=handle)[0].delete()
-        self.emit("source-delete", ([handle],))
-
-    def remove_citation(self, handle, transaction):
-        self.dji.Citation.filter(handle=handle)[0].delete()
-        self.emit("citation-delete", ([handle],))
-
-    def remove_event(self, handle, transaction):
-        self.dji.Event.filter(handle=handle)[0].delete()
-        self.emit("event-delete", ([handle],))
-
-    def remove_object(self, handle, transaction):
-        self.dji.Media.filter(handle=handle)[0].delete()
-        self.emit("media-delete", ([handle],))
-
-    def remove_place(self, handle, transaction):
-        self.dji.Place.filter(handle=handle)[0].delete()
-        self.emit("place-delete", ([handle],))
-
-    def remove_family(self, handle, transaction):
-        self.dji.Family.filter(handle=handle)[0].delete()
-        self.emit("family-delete", ([handle],))
-
-    def remove_repository(self, handle, transaction):
-        self.dji.Repository.filter(handle=handle)[0].delete()
-        self.emit("repository-delete", ([handle],))
-
-    def remove_note(self, handle, transaction):
-        self.dji.Note.filter(handle=handle)[0].delete()
-        self.emit("note-delete", ([handle],))
-
-    def remove_tag(self, handle, transaction):
-        self.dji.Tag.filter(handle=handle)[0].delete()
-        self.emit("tag-delete", ([handle],))
-
-    def remove_from_surname_list(self, person):
-        ## FIXME
-        pass
-    
-    def get_dbname(self):
-        return "Django Database"
-
-    ## missing
-
-    def find_place_child_handles(self, handle):
-        pass
-
-    def get_cursor(self, table, txn=None, update=False, commit=False):
-        pass
-
-    def get_from_name_and_handle(self, table_name, handle):
-        """
-        Returns a gen.lib object (or None) given table_name and
-        handle.
-
-        Examples:
-
-        >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008")
-        >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23")
-        """
-        if table_name in self._tables:
-            return self._tables[table_name]["handle_func"](handle)
-        return None
-
-    def get_number_of_records(self, table):
-        pass
-
-    def get_place_parent_cursor(self):
-        pass
-
-    def get_place_tree_cursor(self):
-        pass
-
-    def get_table_metadata(self, table_name):
-        """Return the metadata for a valid table name."""
-        if table_name in self._tables:
-            return self._tables[table_name]
-        return None
-
-    def get_transaction_class(self):
-        pass
-
-    def undo(self, update_history=True):
-        # FIXME:
-        return self.undodb.undo(update_history)
-
-    def redo(self, update_history=True):
-        # FIXME:
-        return self.undodb.redo(update_history)
-
-    def backup(self):
-        pass
-
-    def restore(self):
-        pass
diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 936d8f534..1542f4c93 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -130,27 +130,33 @@ class Cursor(object):
     def __init__(self, map, func):
         self.map = map
         self.func = func
+        self._iter = self.__iter__()
     def __enter__(self):
         return self
     def __iter__(self):
-        return self.__next__()
-    def __next__(self):
         for item in self.map.keys():
             yield (bytes(item, "utf-8"), self.func(item))
+    def __next__(self):
+        try:
+            return self._iter.__next__()
+        except StopIteration:
+            return None
     def __exit__(self, *args, **kwargs):
         pass
     def iter(self):
         for item in self.map.keys():
             yield (bytes(item, "utf-8"), self.func(item))
-        yield None
     def first(self):
         self._iter = self.__iter__()
-        return self.next()
+        try:
+            return next(self._iter)
+        except:
+            return
     def next(self):
         try:
             return next(self._iter)
         except:
-            return None
+            return
     def close(self):
         pass
 
@@ -1099,73 +1105,83 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return obj.handle
 
     def commit_person(self, person, trans, change_time=None):
-        if person.handle in self.person_map:
-            self.emit("person-update", ([person.handle],))
-        else:
-            self.emit("person-add", ([person.handle],))
+        if not trans.batch:
+            if person.handle in self.person_map:
+                self.emit("person-update", ([person.handle],))
+            else:
+                self.emit("person-add", ([person.handle],))
         self.person_map[person.handle] = person
 
     def commit_family(self, family, trans, change_time=None):
-        if family.handle in self.family_map:
-            self.emit("family-update", ([family.handle],))
-        else:
-            self.emit("family-add", ([family.handle],))
+        if not trans.batch:
+            if family.handle in self.family_map:
+                self.emit("family-update", ([family.handle],))
+            else:
+                self.emit("family-add", ([family.handle],))
         self.family_map[family.handle] = family
 
     def commit_citation(self, citation, trans, change_time=None):
-        if citation.handle in self.citation_map:
-            self.emit("citation-update", ([citation.handle],))
-        else:
-            self.emit("citation-add", ([citation.handle],))
+        if not trans.batch:
+            if citation.handle in self.citation_map:
+                self.emit("citation-update", ([citation.handle],))
+            else:
+                self.emit("citation-add", ([citation.handle],))
         self.citation_map[citation.handle] = citation
 
     def commit_source(self, source, trans, change_time=None):
-        if source.handle in self.source_map:
-            self.emit("source-update", ([source.handle],))
-        else:
-            self.emit("source-add", ([source.handle],))
+        if not trans.batch:
+            if source.handle in self.source_map:
+                self.emit("source-update", ([source.handle],))
+            else:
+                self.emit("source-add", ([source.handle],))
         self.source_map[source.handle] = source
 
     def commit_repository(self, repository, trans, change_time=None):
-        if repository.handle in self.repository_map:
-            self.emit("repository-update", ([repository.handle],))
-        else:
-            self.emit("repository-add", ([repository.handle],))
+        if not trans.batch:
+            if repository.handle in self.repository_map:
+                self.emit("repository-update", ([repository.handle],))
+            else:
+                self.emit("repository-add", ([repository.handle],))
         self.repository_map[repository.handle] = repository
 
     def commit_note(self, note, trans, change_time=None):
-        if note.handle in self.note_map:
-            self.emit("note-update", ([note.handle],))
-        else:
-            self.emit("note-add", ([note.handle],))
+        if not trans.batch:
+            if note.handle in self.note_map:
+                self.emit("note-update", ([note.handle],))
+            else:
+                self.emit("note-add", ([note.handle],))
         self.note_map[note.handle] = note
 
     def commit_place(self, place, trans, change_time=None):
-        if place.handle in self.place_map:
-            self.emit("place-update", ([place.handle],))
-        else:
-            self.emit("place-add", ([place.handle],))
+        if not trans.batch:
+            if place.handle in self.place_map:
+                self.emit("place-update", ([place.handle],))
+            else:
+                self.emit("place-add", ([place.handle],))
         self.place_map[place.handle] = place
 
     def commit_event(self, event, trans, change_time=None):
-        if event.handle in self.event_map:
-            self.emit("event-update", ([event.handle],))
-        else:
-            self.emit("event-add", ([event.handle],))
+        if not trans.batch:
+            if event.handle in self.event_map:
+                self.emit("event-update", ([event.handle],))
+            else:
+                self.emit("event-add", ([event.handle],))
         self.event_map[event.handle] = event
 
     def commit_tag(self, tag, trans, change_time=None):
-        if tag.handle in self.tag_map:
-            self.emit("tag-update", ([tag.handle],))
-        else:
-            self.emit("tag-add", ([tag.handle],))
+        if not trans.batch:
+            if tag.handle in self.tag_map:
+                self.emit("tag-update", ([tag.handle],))
+            else:
+                self.emit("tag-add", ([tag.handle],))
         self.tag_map[tag.handle] = tag
 
     def commit_media_object(self, media, transaction, change_time=None):
-        if media.handle in self.media_map:
-            self.emit("media-update", ([media.handle],))
-        else:
-            self.emit("media-add", ([media.handle],))
+        if not trans.batch:
+            if media.handle in self.media_map:
+                self.emit("media-update", ([media.handle],))
+            else:
+                self.emit("media-add", ([media.handle],))
         self.media_map[media.handle] = media
 
     def get_gramps_ids(self, obj_key):
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index e380c9601..852afe779 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -127,13 +127,17 @@ class Cursor(object):
     def __init__(self, model, func):
         self.model = model
         self.func = func
+        self._iter = self.__iter__()
     def __enter__(self):
         return self
     def __iter__(self):
-        return self.__next__()
-    def __next__(self):
         for item in self.model.all():
             yield (bytes(item.handle, "utf-8"), self.func(item.handle))
+    def __next__(self):
+        try:
+            return self._iter.__next__()
+        except StopIteration:
+            return None
     def __exit__(self, *args, **kwargs):
         pass
     def iter(self):
@@ -142,7 +146,10 @@ class Cursor(object):
         yield None
     def first(self):
         self._iter = self.__iter__()
-        return self.next()
+        try:
+            return next(self._iter)
+        except:
+            return
     def next(self):
         try:
             return next(self._iter)
@@ -1494,10 +1501,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 items[0].delete()
             self.dji.add_person(person.serialize())
             self.dji.add_person_detail(person.serialize())
-            if items.count() > 0:
-                self.emit("person-update", ([person.handle],))
-            else:
-                self.emit("person-add", ([person.handle],))
+            if not trans.batch:
+                if items.count() > 0:
+                    self.emit("person-update", ([person.handle],))
+                else:
+                    self.emit("person-add", ([person.handle],))
 
     def commit_family(self, family, trans, change_time=None):
         if self.use_import_cache:
@@ -1509,10 +1517,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 items[0].delete()
             self.dji.add_family(family.serialize())
             self.dji.add_family_detail(family.serialize())
-            if items.count() > 0:
-                self.emit("family-update", ([family.handle],))
-            else:
-                self.emit("family-add", ([family.handle],))
+            if not trans.batch:
+                if items.count() > 0:
+                    self.emit("family-update", ([family.handle],))
+                else:
+                    self.emit("family-add", ([family.handle],))
 
     def commit_citation(self, citation, trans, change_time=None):
         if self.use_import_cache:
@@ -1524,10 +1533,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 items[0].delete()
             self.dji.add_citation(citation.serialize())
             self.dji.add_citation_detail(citation.serialize())
-            if items.count() > 0:
-                self.emit("citation-update", ([citation.handle],))
-            else:
-                self.emit("citation-add", ([citation.handle],))
+            if not trans.batch:
+                if items.count() > 0:
+                    self.emit("citation-update", ([citation.handle],))
+                else:
+                    self.emit("citation-add", ([citation.handle],))
 
     def commit_source(self, source, trans, change_time=None):
         if self.use_import_cache:
@@ -1539,10 +1549,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 items[0].delete()
             self.dji.add_source(source.serialize())
             self.dji.add_source_detail(source.serialize())
-            if items.count() > 0:
-                self.emit("source-update", ([source.handle],))
-            else:
-                self.emit("source-add", ([source.handle],))
+            if not trans.batch:
+                if items.count() > 0:
+                    self.emit("source-update", ([source.handle],))
+                else:
+                    self.emit("source-add", ([source.handle],))
 
     def commit_repository(self, repository, trans, change_time=None):
         if self.use_import_cache:
@@ -1554,10 +1565,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 items[0].delete()
             self.dji.add_repository(repository.serialize())
             self.dji.add_repository_detail(repository.serialize())
-            if items.count() > 0:
-                self.emit("repository-update", ([repository.handle],))
-            else:
-                self.emit("repository-add", ([repository.handle],))
+            if not trans.batch:
+                if items.count() > 0:
+                    self.emit("repository-update", ([repository.handle],))
+                else:
+                    self.emit("repository-add", ([repository.handle],))
 
     def commit_note(self, note, trans, change_time=None):
         if self.use_import_cache:
@@ -1569,10 +1581,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 items[0].delete()
             self.dji.add_note(note.serialize())
             self.dji.add_note_detail(note.serialize())
-            if items.count() > 0:
-                self.emit("note-update", ([note.handle],))
-            else:
-                self.emit("note-add", ([note.handle],))
+            if not trans.batch:
+                if items.count() > 0:
+                    self.emit("note-update", ([note.handle],))
+                else:
+                    self.emit("note-add", ([note.handle],))
 
     def commit_place(self, place, trans, change_time=None):
         if self.use_import_cache:
@@ -1584,10 +1597,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 items[0].delete()
             self.dji.add_place(place.serialize())
             self.dji.add_place_detail(place.serialize())
-            if items.count() > 0:
-                self.emit("place-update", ([place.handle],))
-            else:
-                self.emit("place-add", ([place.handle],))
+            if not trans.batch:
+                if items.count() > 0:
+                    self.emit("place-update", ([place.handle],))
+                else:
+                    self.emit("place-add", ([place.handle],))
 
     def commit_event(self, event, trans, change_time=None):
         if self.use_import_cache:
@@ -1599,10 +1613,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 items[0].delete()
             self.dji.add_event(event.serialize())
             self.dji.add_event_detail(event.serialize())
-            if items.count() > 0:
-                self.emit("event-update", ([event.handle],))
-            else:
-                self.emit("event-add", ([event.handle],))
+            if not trans.batch:
+                if items.count() > 0:
+                    self.emit("event-update", ([event.handle],))
+                else:
+                    self.emit("event-add", ([event.handle],))
 
     def commit_tag(self, tag, trans, change_time=None):
         if self.use_import_cache:
@@ -1614,10 +1629,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 items[0].delete()
             self.dji.add_tag(tag.serialize())
             self.dji.add_tag_detail(tag.serialize())
-            if items.count() > 0:
-                self.emit("tag-update", ([tag.handle],))
-            else:
-                self.emit("tag-add", ([tag.handle],))
+            if not trans.batch:
+                if items.count() > 0:
+                    self.emit("tag-update", ([tag.handle],))
+                else:
+                    self.emit("tag-add", ([tag.handle],))
 
     def commit_media_object(self, media, transaction, change_time=None):
         """
@@ -1633,10 +1649,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 items[0].delete()
             self.dji.add_media(media.serialize())
             self.dji.add_media_detail(media.serialize())
-            if items.count() > 0:
-                self.emit("media-update", ([media.handle],))
-            else:
-                self.emit("media-add", ([media.handle],))
+            if not trans.batch:
+                if items.count() > 0:
+                    self.emit("media-update", ([media.handle],))
+                else:
+                    self.emit("media-add", ([media.handle],))
 
     def get_gramps_ids(self, obj_key):
         key2table = {

From d72ed91f21a86e707bcc8f8fdaecee80d2d1625f Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Thu, 14 May 2015 11:49:58 -0400
Subject: [PATCH 064/105] DictionaryDb: emit add after actually adding

---
 gramps/plugins/database/dictionarydb.py | 80 ++++++++++++++++++-------
 1 file changed, 60 insertions(+), 20 deletions(-)

diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 1542f4c93..15009d584 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -1105,84 +1105,124 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return obj.handle
 
     def commit_person(self, person, trans, change_time=None):
+        emit = None
         if not trans.batch:
             if person.handle in self.person_map:
-                self.emit("person-update", ([person.handle],))
+                emit = "person-update"
             else:
-                self.emit("person-add", ([person.handle],))
+                emit = "person-add"
         self.person_map[person.handle] = person
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([person.handle],))
 
     def commit_family(self, family, trans, change_time=None):
+        emit = None
         if not trans.batch:
             if family.handle in self.family_map:
-                self.emit("family-update", ([family.handle],))
+                emit = "family-update"
             else:
-                self.emit("family-add", ([family.handle],))
+                emit = "family-add"
         self.family_map[family.handle] = family
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([family.handle],))
 
     def commit_citation(self, citation, trans, change_time=None):
+        emit = None
         if not trans.batch:
             if citation.handle in self.citation_map:
-                self.emit("citation-update", ([citation.handle],))
+                emit = "citation-update"
             else:
-                self.emit("citation-add", ([citation.handle],))
+                emit = "citation-add"
         self.citation_map[citation.handle] = citation
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([citation.handle],))
 
     def commit_source(self, source, trans, change_time=None):
+        emit = None
         if not trans.batch:
             if source.handle in self.source_map:
-                self.emit("source-update", ([source.handle],))
+                emit = "source-update"
             else:
-                self.emit("source-add", ([source.handle],))
+                emit = "source-add"
         self.source_map[source.handle] = source
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([source.handle],))
 
     def commit_repository(self, repository, trans, change_time=None):
+        emit = None
         if not trans.batch:
             if repository.handle in self.repository_map:
-                self.emit("repository-update", ([repository.handle],))
+                emit = "repository-update"
             else:
-                self.emit("repository-add", ([repository.handle],))
+                emit = "repository-add"
         self.repository_map[repository.handle] = repository
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([repository.handle],))
 
     def commit_note(self, note, trans, change_time=None):
+        emit = None
         if not trans.batch:
             if note.handle in self.note_map:
-                self.emit("note-update", ([note.handle],))
+                emit "note-update"
             else:
-                self.emit("note-add", ([note.handle],))
+                emit = "note-add"
         self.note_map[note.handle] = note
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([note.handle],))
 
     def commit_place(self, place, trans, change_time=None):
+        emit = None
         if not trans.batch:
             if place.handle in self.place_map:
-                self.emit("place-update", ([place.handle],))
+                emit = "place-update"
             else:
-                self.emit("place-add", ([place.handle],))
+                emit = "place-add"
         self.place_map[place.handle] = place
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([place.handle],))
 
     def commit_event(self, event, trans, change_time=None):
+        emit = None
         if not trans.batch:
             if event.handle in self.event_map:
-                self.emit("event-update", ([event.handle],))
+                emit = "event-update"
             else:
-                self.emit("event-add", ([event.handle],))
+                emit = "event-add"
         self.event_map[event.handle] = event
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([event.handle],))
 
     def commit_tag(self, tag, trans, change_time=None):
+        emit = None
         if not trans.batch:
             if tag.handle in self.tag_map:
-                self.emit("tag-update", ([tag.handle],))
+                emit = "tag-update"
             else:
-                self.emit("tag-add", ([tag.handle],))
+                emit = "tag-add"
         self.tag_map[tag.handle] = tag
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([tag.handle],))
 
     def commit_media_object(self, media, transaction, change_time=None):
+        emit = None
         if not trans.batch:
             if media.handle in self.media_map:
-                self.emit("media-update", ([media.handle],))
+                emit = "media-update"
             else:
-                self.emit("media-add", ([media.handle],))
+                emit = "media-add"
         self.media_map[media.handle] = media
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([media.handle],))
 
     def get_gramps_ids(self, obj_key):
         key2table = {

From d6d5ecdf5d36cad99a0da616f999769150f29c86 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Thu, 14 May 2015 11:51:56 -0400
Subject: [PATCH 065/105] DictionaryDb: emit add after actually adding (fixed
 typo)

---
 gramps/plugins/database/dictionarydb.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 15009d584..5c167fe48 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -1168,7 +1168,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         emit = None
         if not trans.batch:
             if note.handle in self.note_map:
-                emit "note-update"
+                emit = "note-update"
             else:
                 emit = "note-add"
         self.note_map[note.handle] = note

From bf12a2bc6762668e10f802e314be0eed451b6cea Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Thu, 14 May 2015 12:30:30 -0400
Subject: [PATCH 066/105] Basic infrastructure for Undo/Redo

---
 gramps/gen/db/undoredo.py               | 136 ++++++++++++++++++++++++
 gramps/plugins/database/dictionarydb.py |   5 +-
 gramps/plugins/database/djangodb.py     |   3 +
 3 files changed, 143 insertions(+), 1 deletion(-)
 create mode 100644 gramps/gen/db/undoredo.py

diff --git a/gramps/gen/db/undoredo.py b/gramps/gen/db/undoredo.py
new file mode 100644
index 000000000..b12735093
--- /dev/null
+++ b/gramps/gen/db/undoredo.py
@@ -0,0 +1,136 @@
+import time
+from collections import deque
+
+class DbUndo(object):
+    """
+    Base class for the Gramps undo/redo manager.  Needs to be subclassed
+    for use with a real backend.
+    """
+
+    __slots__ = ('undodb', 'db', 'mapbase', 'undo_history_timestamp',
+                 'txn', 'undoq', 'redoq')
+
+    def __init__(self, grampsdb):
+        """
+        Class constructor. Set up main instance variables
+        """
+        self.db = grampsdb
+        self.undoq = deque()
+        self.redoq = deque()
+        self.undo_history_timestamp = time.time()
+        self.txn = None
+
+    def clear(self):
+        """
+        Clear the undo/redo list (but not the backing storage)
+        """
+        self.undoq.clear()
+        self.redoq.clear()
+        self.undo_history_timestamp = time.time()
+        self.txn = None
+
+    def __enter__(self, value):
+        """
+        Context manager method to establish the context
+        """
+        self.open(value)
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        """
+        Context manager method to finish the context
+        """
+        if exc_type is None:
+            self.close()
+        return exc_type is None
+
+    def open(self, value):
+        """
+        Open the backing storage.  Needs to be overridden in the derived
+        class.
+        """
+        raise NotImplementedError
+
+    def close(self):
+        """
+        Close the backing storage.  Needs to be overridden in the derived
+        class.
+        """        
+        raise NotImplementedError
+
+    def append(self, value):
+        """
+        Add a new entry on the end.  Needs to be overridden in the derived
+        class.
+        """        
+        raise NotImplementedError
+
+    def __getitem__(self, index):
+        """
+        Returns an entry by index number.  Needs to be overridden in the
+        derived class.
+        """        
+        raise NotImplementedError
+
+    def __setitem__(self, index, value):
+        """
+        Set an entry to a value.  Needs to be overridden in the derived class.
+        """           
+        raise NotImplementedError
+
+    def __len__(self):
+        """
+        Returns the number of entries.  Needs to be overridden in the derived
+        class.
+        """         
+        raise NotImplementedError
+
+    def commit(self, txn, msg):
+        """
+        Commit the transaction to the undo/redo database.  "txn" should be
+        an instance of Gramps transaction class
+        """
+        txn.set_description(msg)
+        txn.timestamp = time.time()
+        self.undoq.append(txn)
+
+    def undo(self, update_history=True):
+        """
+        Undo a previously committed transaction
+        """
+        if self.db.readonly or self.undo_count == 0:
+            return False
+        return self.__undo(update_history)
+
+    def redo(self, update_history=True):
+        """
+        Redo a previously committed, then undone, transaction
+        """
+        if self.db.readonly or self.redo_count == 0:
+            return False
+        return self.__redo(update_history)
+
+    def undoredo(func):
+        """
+        Decorator function to wrap undo and redo operations within a bsddb
+        transaction.  It also catches bsddb errors and raises an exception
+        as appropriate
+        """
+        pass
+
+    def __redo(self, update_history=True):
+        """
+        Access the last undone transaction, and revert the data to the state 
+        before the transaction was undone.
+        """
+        pass
+
+    def __undo(self, db=None, update_history=True):
+        """
+        Access the last committed transaction, and revert the data to the 
+        state before the transaction was committed.
+        """
+        pass
+
+    undo_count = property(lambda self:len(self.undoq))
+    redo_count = property(lambda self:len(self.redoq))
diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 5c167fe48..09748295a 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -37,6 +37,7 @@ import logging
 #
 #------------------------------------------------------------------------
 from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn, KEY_TO_NAME_MAP
+from gramps.gen.db.undoredo import DbUndo
 from gramps.gen.db.dbconst import *
 from gramps.gen.utils.callback import Callback
 from gramps.gen.updatecallback import UpdateCallback
@@ -390,6 +391,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.modified   = 0
         self.txn = DictionaryTxn("DbDictionary Transaction", self)
         self.transaction = None
+        self.undodb = DbUndo(self)
+        self.abort_possible = False
         self._directory = directory
         if directory:
             self.load(directory)
@@ -1212,7 +1215,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if emit:
             self.emit(emit, ([tag.handle],))
 
-    def commit_media_object(self, media, transaction, change_time=None):
+    def commit_media_object(self, media, trans, change_time=None):
         emit = None
         if not trans.batch:
             if media.handle in self.media_map:
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index 852afe779..0a88733cd 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -44,6 +44,7 @@ from gramps.gen.lib import (Person, Family, Event, Place, Repository,
                             Citation, Source, Note, MediaObject, Tag, 
                             Researcher)
 from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn
+from gramps.gen.db.undoredo import DbUndo
 from gramps.gen.utils.callback import Callback
 from gramps.gen.updatecallback import UpdateCallback
 from gramps.gen.db import (PERSON_KEY,
@@ -335,6 +336,8 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.import_cache = {}
         self.use_import_cache = False
         self.use_db_cache = True
+        self.undodb = DbUndo(self)
+        self.abort_possible = False
         self._directory = directory
         if directory:
             self.load(directory)

From b2ed5d1cbb49e6b7caa83cd70fe8bb6d5db27d1b Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Thu, 14 May 2015 12:43:24 -0400
Subject: [PATCH 067/105] DjangoDb: send proper object-add signal on new
 objects

---
 gramps/plugins/database/djangodb.py | 50 +++++++++++++++++------------
 1 file changed, 30 insertions(+), 20 deletions(-)

diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index 0a88733cd..305cde2a8 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -1499,13 +1499,14 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         else:
             raw = person.serialize()
             items = self.dji.Person.filter(handle=person.handle)
-            if items.count() > 0:
+            count = items.count()
+            if count > 0:
                 # Hack, for the moment: delete and re-add
                 items[0].delete()
             self.dji.add_person(person.serialize())
             self.dji.add_person_detail(person.serialize())
             if not trans.batch:
-                if items.count() > 0:
+                if count > 0:
                     self.emit("person-update", ([person.handle],))
                 else:
                     self.emit("person-add", ([person.handle],))
@@ -1516,12 +1517,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         else:
             raw = family.serialize()
             items = self.dji.Family.filter(handle=family.handle)
-            if items.count() > 0:
+            count = items.count()
+            if count > 0:
                 items[0].delete()
             self.dji.add_family(family.serialize())
             self.dji.add_family_detail(family.serialize())
             if not trans.batch:
-                if items.count() > 0:
+                if count > 0:
                     self.emit("family-update", ([family.handle],))
                 else:
                     self.emit("family-add", ([family.handle],))
@@ -1532,12 +1534,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         else:
             raw = citation.serialize()
             items = self.dji.Citation.filter(handle=citation.handle)
-            if items.count() > 0:
+            count = items.count()
+            if count > 0:
                 items[0].delete()
             self.dji.add_citation(citation.serialize())
             self.dji.add_citation_detail(citation.serialize())
             if not trans.batch:
-                if items.count() > 0:
+                if count > 0:
                     self.emit("citation-update", ([citation.handle],))
                 else:
                     self.emit("citation-add", ([citation.handle],))
@@ -1548,12 +1551,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         else:
             raw = source.serialize()
             items = self.dji.Source.filter(handle=source.handle)
-            if items.count() > 0:
+            count = items.count()
+            if count > 0:
                 items[0].delete()
             self.dji.add_source(source.serialize())
             self.dji.add_source_detail(source.serialize())
             if not trans.batch:
-                if items.count() > 0:
+                if count > 0:
                     self.emit("source-update", ([source.handle],))
                 else:
                     self.emit("source-add", ([source.handle],))
@@ -1564,12 +1568,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         else:
             raw = repository.serialize()
             items = self.dji.Repository.filter(handle=repository.handle)
-            if items.count() > 0:
+            count = items.count()
+            if count > 0:
                 items[0].delete()
             self.dji.add_repository(repository.serialize())
             self.dji.add_repository_detail(repository.serialize())
             if not trans.batch:
-                if items.count() > 0:
+                if count > 0:
                     self.emit("repository-update", ([repository.handle],))
                 else:
                     self.emit("repository-add", ([repository.handle],))
@@ -1580,12 +1585,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         else:
             raw = note.serialize()
             items = self.dji.Note.filter(handle=note.handle)
-            if items.count() > 0:
+            count = items.count()
+            if count > 0:
                 items[0].delete()
             self.dji.add_note(note.serialize())
             self.dji.add_note_detail(note.serialize())
             if not trans.batch:
-                if items.count() > 0:
+                if count > 0:
                     self.emit("note-update", ([note.handle],))
                 else:
                     self.emit("note-add", ([note.handle],))
@@ -1596,12 +1602,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         else:
             raw = place.serialize()
             items = self.dji.Place.filter(handle=place.handle)
-            if items.count() > 0:
+            count = items.count()
+            if count > 0:
                 items[0].delete()
             self.dji.add_place(place.serialize())
             self.dji.add_place_detail(place.serialize())
             if not trans.batch:
-                if items.count() > 0:
+                if count > 0:
                     self.emit("place-update", ([place.handle],))
                 else:
                     self.emit("place-add", ([place.handle],))
@@ -1612,12 +1619,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         else:
             raw = event.serialize()
             items = self.dji.Event.filter(handle=event.handle)
-            if items.count() > 0:
+            count = items.count()
+            if count > 0:
                 items[0].delete()
             self.dji.add_event(event.serialize())
             self.dji.add_event_detail(event.serialize())
             if not trans.batch:
-                if items.count() > 0:
+                if count > 0:
                     self.emit("event-update", ([event.handle],))
                 else:
                     self.emit("event-add", ([event.handle],))
@@ -1628,12 +1636,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         else:
             raw = tag.serialize()
             items = self.dji.Tag.filter(handle=tag.handle)
-            if items.count() > 0:
+            count = items.count()
+            if count > 0:
                 items[0].delete()
             self.dji.add_tag(tag.serialize())
             self.dji.add_tag_detail(tag.serialize())
             if not trans.batch:
-                if items.count() > 0:
+                if count > 0:
                     self.emit("tag-update", ([tag.handle],))
                 else:
                     self.emit("tag-add", ([tag.handle],))
@@ -1648,12 +1657,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         else:
             raw = media.serialize()
             items = self.dji.Media.filter(handle=media.handle)
-            if items.count() > 0:
+            count = items.count()
+            if count > 0:
                 items[0].delete()
             self.dji.add_media(media.serialize())
             self.dji.add_media_detail(media.serialize())
             if not trans.batch:
-                if items.count() > 0:
+                if count > 0:
                     self.emit("media-update", ([media.handle],))
                 else:
                     self.emit("media-add", ([media.handle],))

From e4d05f301ae4e1d57a6730d22d5f6f52ff0b118d Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Thu, 14 May 2015 12:48:55 -0400
Subject: [PATCH 068/105] Fixed About dialog to show proper BSDDB version

---
 gramps/gui/aboutdialog.py | 28 ++++++++++++++--------------
 1 file changed, 14 insertions(+), 14 deletions(-)

diff --git a/gramps/gui/aboutdialog.py b/gramps/gui/aboutdialog.py
index 9886b550f..bf0fc7053 100644
--- a/gramps/gui/aboutdialog.py
+++ b/gramps/gui/aboutdialog.py
@@ -29,12 +29,6 @@ import os
 import sys
 import io
 
-try:
-    import bsddb3 as bsddb ## ok, in try/except
-    BSDDB_STR = ellipses(str(bsddb.__version__) + " " + str(bsddb.db.version()))
-except:
-    BSDDB_STR = 'not found'
-
 ##import logging
 ##_LOG = logging.getLogger(".GrampsAboutDialog")
 
@@ -65,6 +59,20 @@ _ = glocale.translation.gettext
 from gramps.gen.constfunc import get_env_var
 from .display import display_url
 
+def ellipses(text):
+    """
+    Ellipsize text on length 40
+    """
+    if len(text) > 40:
+        return text[:40] + "..."
+    return text
+
+try:
+    import bsddb3 as bsddb ## ok, in try/except
+    BSDDB_STR = ellipses(str(bsddb.__version__) + " " + str(bsddb.db.version()))
+except:
+    BSDDB_STR = 'not found'
+
 #-------------------------------------------------------------------------
 #
 # GrampsAboutDialog
@@ -135,14 +143,6 @@ class GrampsAboutDialog(Gtk.AboutDialog):
                    ellipses(operatingsystem),
                    ellipses(distribution)))
 
-def ellipses(text):
-    """
-    Ellipsize text on length 40
-    """
-    if len(text) > 40:
-        return text[:40] + "..."
-    return text
-
 #-------------------------------------------------------------------------
 #
 # AuthorParser

From 331a947ea47ce15113d79d223c8cc385a8308221 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Thu, 14 May 2015 22:35:50 -0400
Subject: [PATCH 069/105] Removed hardcoded database backend types

---
 gramps/gui/dbman.py | 43 +++++++++++++++++++++++++++----------------
 1 file changed, 27 insertions(+), 16 deletions(-)

diff --git a/gramps/gui/dbman.py b/gramps/gui/dbman.py
index 0891170f4..210af49fb 100644
--- a/gramps/gui/dbman.py
+++ b/gramps/gui/dbman.py
@@ -69,6 +69,7 @@ from gi.repository import Pango
 #
 #-------------------------------------------------------------------------
 from gramps.gen.const import GRAMPS_LOCALE as glocale
+from gramps.gen.plug import BasePluginManager
 _ = glocale.translation.gettext
 from gramps.gen.const import URL_WIKISTRING
 from .user import User
@@ -104,7 +105,10 @@ ICON_COL = 6
 RCS_BUTTON = { True : _('_Extract'), False : _('_Archive') }
 
 class DatabaseDialog(Gtk.MessageDialog):
-    def __init__(self, parent=None):
+    def __init__(self, parent, options):
+        """
+        options = [(pdata, number), ...]
+        """
         Gtk.MessageDialog.__init__(self,
                                 parent,
                                 flags=Gtk.DialogFlags.MODAL,
@@ -112,15 +116,12 @@ class DatabaseDialog(Gtk.MessageDialog):
                                    )
         self.set_icon(ICON)
         self.set_title('')
-
         self.set_markup('<span size="larger" weight="bold">%s</span>' %
-                        _('Database Backend'))
+                        _('Database Backend for New Tree'))
         self.format_secondary_text(
-            _("Please select a database backend type"))
-
-        self.add_button("BSDDB Database (standard)", 1)
-        self.add_button("Dictionary (in-memory)", 2)
-        self.add_button("Django Database", 3)
+            _("Please select a database backend type:"))
+        for option, number in options:
+            self.add_button(option.name, number)
 
 class DbManager(CLIDbManager):
     """
@@ -779,14 +780,24 @@ class DbManager(CLIDbManager):
         message.
         """
         self.new.set_sensitive(False)
-        # popup window and ask for dbid types, if more than one
-        ## FIXME: autoload from plugins
-        dbid = "bsddb"
-        d = DatabaseDialog(self.top)
-        database = d.run()
-        d.destroy()
-        if database >= 0:
-            dbid = {1:"bsddb",2:"dictionarydb",3:"djangodb"}[database]
+        dbid = None
+        pmgr = BasePluginManager.get_instance()
+        pdata = pmgr.get_reg_databases()
+        # If just one database backend, just use it:
+        if len(pdata) == 0:
+            DbManager.ERROR(_("No available database backends"),
+                            _("Please check your dependencies."))
+        elif len(pdata) == 1:
+            dbid = pdata[0].id
+        elif len(pdata) > 1:
+            options = sorted(list(zip(pdata, range(1, len(pdata) + 1))), key=lambda items: items[0].name)
+            d = DatabaseDialog(self.top, options)
+            number = d.run()
+            d.destroy()
+            if number >= 0:
+                dbid = [option[0].id for option in options if option[1] == number][0]
+        ### Now, let's load it up
+        if dbid:
             try:
                 self._create_new_db(dbid=dbid)
             except (OSError, IOError) as msg:

From 11ac0f15510204a2fa29d5f3b9064817319ec7d4 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Thu, 14 May 2015 22:52:57 -0400
Subject: [PATCH 070/105] Database plugin type support reset_system, to reset
 modules

---
 gramps/gen/dbstate.py                   | 20 +++++++++-----------
 gramps/gen/plug/_pluginreg.py           | 13 +++++++++++++
 gramps/plugins/database/djangodb.gpr.py |  1 +
 3 files changed, 23 insertions(+), 11 deletions(-)

diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py
index 069934a13..0d9c30d4a 100644
--- a/gramps/gen/dbstate.py
+++ b/gramps/gen/dbstate.py
@@ -140,18 +140,16 @@ class DbState(Callback):
             pmgr.reg_plugins(USER_PLUGINS, self, None, load_on_reg=True)
             pdata = pmgr.get_plugin(id)
 
-        ### FIXME: Currently Django needs to reset modules
-        if pdata.id == "djangodb":
-            if self.modules_is_set():
-                self.reset_modules()
-            else:
-                self.save_modules()
+        if pdata:
+            if pdata.reset_system:
+                if self.modules_is_set():
+                    self.reset_modules()
+                else:
+                    self.save_modules()
+            mod = pmgr.load_plugin(pdata)
+            database = getattr(mod, pdata.databaseclass)
+            return database()
 
-        mod = pmgr.load_plugin(pdata)
-        database = getattr(mod, pdata.databaseclass)
-        return database()
-
-    ## FIXME:
     ## Work-around for databases that need sys refresh (django):
     def modules_is_set(self):
         if hasattr(self, "_modules"):
diff --git a/gramps/gen/plug/_pluginreg.py b/gramps/gen/plug/_pluginreg.py
index 5619f18ed..70a93f0f2 100644
--- a/gramps/gen/plug/_pluginreg.py
+++ b/gramps/gen/plug/_pluginreg.py
@@ -356,6 +356,9 @@ class PluginData(object):
 
     .. attribute:: databaseclass
        The class in the module that is the database class
+    .. attribute:: reset_system
+       Boolean to indicate that the system (sys.modules) should
+       be reset.
     """
 
     def __init__(self):
@@ -430,6 +433,7 @@ class PluginData(object):
         self._order = END
         #DATABASE attr
         self._databaseclass = None
+        self._reset_system = False
         #GENERAL attr
         self._data = []
         self._process = None
@@ -949,7 +953,16 @@ class PluginData(object):
     def _get_databaseclass(self):
         return self._databaseclass
 
+    def _set_reset_system(self, reset_system):
+        if not self._ptype == DATABASE:
+            raise ValueError('reset_system may only be set for DATABASE plugins')
+        self._reset_system = reset_system
+
+    def _get_reset_system(self):
+        return self._reset_system
+
     databaseclass = property(_get_databaseclass, _set_databaseclass)
+    reset_system = property(_get_reset_system, _set_reset_system)
 
     #GENERAL attr
     def _set_data(self, data):
diff --git a/gramps/plugins/database/djangodb.gpr.py b/gramps/plugins/database/djangodb.gpr.py
index 57b30f2f5..2d0d0797b 100644
--- a/gramps/plugins/database/djangodb.gpr.py
+++ b/gramps/plugins/database/djangodb.gpr.py
@@ -29,3 +29,4 @@ plg.status = STABLE
 plg.fname = 'djangodb.py'
 plg.ptype = DATABASE
 plg.databaseclass = 'DbDjango'
+plg.reset_system = True

From 5171b3748d9277b7bc22a58d7b006e6e9875ba8a Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Thu, 14 May 2015 23:15:30 -0400
Subject: [PATCH 071/105] Added missing bookmark count methods to djangodb and
 dictionarydb

---
 gramps/plugins/database/dictionarydb.py | 13 +++++++++++++
 gramps/plugins/database/djangodb.py     | 14 ++++++++++++++
 2 files changed, 27 insertions(+)

diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 09748295a..2a15c28b1 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -393,6 +393,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.transaction = None
         self.undodb = DbUndo(self)
         self.abort_possible = False
+        self._bm_changes = 0
         self._directory = directory
         if directory:
             self.load(directory)
@@ -1689,3 +1690,15 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         with open(versionpath, "w") as version_file:
             version_file.write("dictionarydb")
 
+    def report_bm_change(self):
+        """
+        Add 1 to the number of bookmark changes during this session.
+        """
+        self._bm_changes += 1
+
+    def db_has_bm_changes(self):
+        """
+        Return whethere there were bookmark changes during the session.
+        """
+        return self._bm_changes > 0
+
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index 305cde2a8..543243c99 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -338,6 +338,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.use_db_cache = True
         self.undodb = DbUndo(self)
         self.abort_possible = False
+        self._bm_changes = 0
         self._directory = directory
         if directory:
             self.load(directory)
@@ -2088,3 +2089,16 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         for filename in os.listdir(defaults):
             fullpath = os.path.abspath(os.path.join(defaults, filename))
             shutil.copy2(fullpath, directory)
+
+    def report_bm_change(self):
+        """
+        Add 1 to the number of bookmark changes during this session.
+        """
+        self._bm_changes += 1
+
+    def db_has_bm_changes(self):
+        """
+        Return whethere there were bookmark changes during the session.
+        """
+        return self._bm_changes > 0
+

From 3c52f7016b5e9f8c3f23d0eee3064e449f4bd2ed Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Fri, 15 May 2015 06:42:13 -0400
Subject: [PATCH 072/105] Alternative DBs: touch meta_data.db to record last
 access time

---
 gramps/plugins/database/dictionarydb.py |  9 +++++++++
 gramps/plugins/database/djangodb.py     | 11 ++++++++++-
 2 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 2a15c28b1..ff1b8f745 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -67,6 +67,13 @@ from gramps.gen.lib.tag import Tag
 
 _LOG = logging.getLogger(DBLOGNAME)
 
+def touch(fname, mode=0o666, dir_fd=None, **kwargs):
+    ## After http://stackoverflow.com/questions/1158076/implement-touch-using-python
+    flags = os.O_CREAT | os.O_APPEND
+    with os.fdopen(os.open(fname, flags=flags, mode=mode, dir_fd=dir_fd)) as f:
+        os.utime(f.fileno() if os.utime in os.supports_fd else fname,
+                 dir_fd=None if os.supports_fd else dir_fd, **kwargs)
+
 class Environment(object):
     """
     Implements the Environment API.
@@ -1483,6 +1490,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             writer = XmlWriter(self, User(), strip_photos=0, compress=1)
             filename = os.path.join(self._directory, "data.gramps")
             writer.write(filename)
+            filename = os.path.join(self._directory, "meta_data.db")
+            touch(filename)
 
     def find_backlink_handles(self, handle, include_classes=None):
         return []
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index 543243c99..759d366b8 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -64,6 +64,13 @@ sys.path.append(os.path.dirname(os.path.abspath(__file__)))
 
 _LOG = logging.getLogger(DBLOGNAME)
 
+def touch(fname, mode=0o666, dir_fd=None, **kwargs):
+    ## After http://stackoverflow.com/questions/1158076/implement-touch-using-python
+    flags = os.O_CREAT | os.O_APPEND
+    with os.fdopen(os.open(fname, flags=flags, mode=mode, dir_fd=dir_fd)) as f:
+        os.utime(f.fileno() if os.utime in os.supports_fd else fname,
+                 dir_fd=None if os.supports_fd else dir_fd, **kwargs)
+
 class Environment(object):
     """
     Implements the Environment API.
@@ -1959,7 +1966,9 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return None
 
     def close(self):
-        pass
+        if self._directory:
+            filename = os.path.join(self._directory, "meta_data.db")
+            touch(filename)
 
     def get_surname_list(self):
         return []

From ea996cf6ddca2cad2a88630f5c4063f51ad718e8 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Fri, 15 May 2015 07:32:03 -0400
Subject: [PATCH 073/105] Database API, -L: database reports summary, if
 possible

---
 gramps/cli/clidbman.py                        | 54 ++++++++++++-------
 gramps/plugins/database/bsddb_support/read.py | 13 +++++
 gramps/plugins/database/dictionarydb.py       | 29 ++++++++++
 gramps/plugins/database/djangodb.py           | 32 +++++++++--
 4 files changed, 107 insertions(+), 21 deletions(-)

diff --git a/gramps/cli/clidbman.py b/gramps/cli/clidbman.py
index 893a2d84f..5bb87e632 100644
--- a/gramps/cli/clidbman.py
+++ b/gramps/cli/clidbman.py
@@ -133,12 +133,42 @@ class CLIDbManager(object):
 
     def get_dbdir_summary(self, dirpath, name):
         """
-        Returns (people_count, bsddb_version, schema_version) of
-        current DB.
-        Returns ("Unknown", "Unknown", "Unknown") if invalid DB or other error.
+        dirpath: full path to database
+        name: proper name of family tree
+
+        Returns dictionary of summary item.
+        Should include at least, if possible:
+
+        _("Path")
+        _("Family Tree")
+        _("Last accessed")
+        _("Database backend")
+        _("Locked?")
+
+        and these details:
+
+        _("Number of people")
+        _("Version")
+        _("Schema version")
         """
-        ## Maybe return the txt file contents, for now
-        return ("Unknown", "Unknown", "Unknown")
+        dbid = "bsddb"
+        dbid_path = os.path.join(dirpath, "database.txt")
+        if os.path.isfile(dbid_path):
+            dbid = open(dbid_path).read().strip()
+        try:
+            database = self.dbstate.make_database(dbid)
+            database.load(dirpath, None)
+            retval = database.get_summary()
+        except Exception as msg:
+            retval = {"Unavailable": str(msg)[:74] + "..."}
+        retval.update({
+            _("Family Tree"): name,
+            _("Path"): dirpath,
+            _("Database backend"): dbid,
+            _("Last accessed"): time_val(dirpath)[1],
+            _("Locked?"): self.is_locked(dirpath),
+        })
+        return retval
 
     def family_tree_summary(self):
         """
@@ -149,19 +179,7 @@ class CLIDbManager(object):
         for item in self.current_names:
             (name, dirpath, path_name, last, 
              tval, enable, stock_id) = item
-            count, bsddb_version, schema_version = self.get_dbdir_summary(dirpath, name)
-            retval = {}
-            retval[_("Number of people")] = count
-            if enable:
-                retval[_("Locked?")] = _("yes")
-            else:
-                retval[_("Locked?")] = _("no")
-            retval[_("Bsddb version")] = bsddb_version
-            retval[_("Schema version")] = schema_version
-            retval[_("Family Tree")] = name
-            retval[_("Path")] = dirpath
-            retval[_("Last accessed")] = time.strftime('%x %X', 
-                                                    time.localtime(tval))
+            retval = self.get_dbdir_summary(dirpath, name)
             summary_list.append( retval )
         return summary_list
 
diff --git a/gramps/plugins/database/bsddb_support/read.py b/gramps/plugins/database/bsddb_support/read.py
index d51810581..9280dbc09 100644
--- a/gramps/plugins/database/bsddb_support/read.py
+++ b/gramps/plugins/database/bsddb_support/read.py
@@ -1976,3 +1976,16 @@ class DbBsddbRead(DbReadBase, Callback):
             self.__log_error()
             name = None
         return name
+
+    def get_summary(self):
+        """
+        Returns dictionary of summary item.
+        Should include, if possible:
+
+        _("Number of people")
+        _("Version")
+        _("Schema version")
+        """
+        return {
+            _("Number of people"): self.get_number_of_people(),
+        }
diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index ff1b8f745..72a383469 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -36,6 +36,8 @@ import logging
 # Gramps Modules
 #
 #------------------------------------------------------------------------
+from gramps.gen.const import GRAMPS_LOCALE as glocale
+_ = glocale.translation.gettext
 from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn, KEY_TO_NAME_MAP
 from gramps.gen.db.undoredo import DbUndo
 from gramps.gen.db.dbconst import *
@@ -1711,3 +1713,30 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         """
         return self._bm_changes > 0
 
+    def get_summary(self):
+        """
+        Returns dictionary of summary item.
+        Should include, if possible:
+
+        _("Number of people")
+        _("Version")
+        _("Schema version")
+        """
+        return {
+            _("Number of people"): self.get_number_of_people(),
+        }
+
+    def get_dbname(self):
+        """
+        In DictionaryDb, the database is in a text file at the path
+        """
+        filepath = os.path.join(self._directory, "name.txt")
+        try:
+            name_file = open(filepath, "r")
+            name = name_file.readline().strip()
+            name_file.close()
+        except (OSError, IOError) as msg:
+            _LOG.error(str(msg))
+            name = None
+        return name
+
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index 759d366b8..1c6e3a6a1 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -40,6 +40,8 @@ from django.db import transaction
 #
 #------------------------------------------------------------------------
 import gramps
+from gramps.gen.const import GRAMPS_LOCALE as glocale
+_ = glocale.translation.gettext
 from gramps.gen.lib import (Person, Family, Event, Place, Repository, 
                             Citation, Source, Note, MediaObject, Tag, 
                             Researcher)
@@ -2028,9 +2030,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         ## FIXME
         pass
     
-    def get_dbname(self):
-        return "Django Database"
-
     ## missing
 
     def find_place_child_handles(self, handle):
@@ -2111,3 +2110,30 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         """
         return self._bm_changes > 0
 
+    def get_summary(self):
+        """
+        Returns dictionary of summary item.
+        Should include, if possible:
+
+        _("Number of people")
+        _("Version")
+        _("Schema version")
+        """
+        return {
+            _("Number of people"): self.get_number_of_people(),
+        }
+
+    def get_dbname(self):
+        """
+        In Django, the database is in a text file at the path
+        """
+        filepath = os.path.join(self._directory, "name.txt")
+        try:
+            name_file = open(filepath, "r")
+            name = name_file.readline().strip()
+            name_file.close()
+        except (OSError, IOError) as msg:
+            _LOG.error(str(msg))
+            name = None
+        return name
+

From c72d40aa07498856ab864af32fa55770080ed5ea Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Fri, 15 May 2015 09:12:21 -0400
Subject: [PATCH 074/105] bsddb backend: supply version details in get_summary

---
 .../plugins/database/bsddb_support/write.py   | 22 +++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/gramps/plugins/database/bsddb_support/write.py b/gramps/plugins/database/bsddb_support/write.py
index 66d4e4a53..46430ee85 100644
--- a/gramps/plugins/database/bsddb_support/write.py
+++ b/gramps/plugins/database/bsddb_support/write.py
@@ -2450,6 +2450,28 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
         except (OSError, IOError) as msg:
             raise DbException(str(msg))
 
+    def get_summary(self):
+        """
+        Returns dictionary of summary item.
+        Should include, if possible:
+
+        _("Number of people")
+        _("Version")
+        _("Schema version")
+        """
+        schema_version = self.metadata.get(b'version', default=None)
+        bdbversion_file = os.path.join(self.path, BDBVERSFN)
+        if os.path.isfile(bdbversion_file):
+            vers_file = open(bdbversion_file)
+            bsddb_version = vers_file.readline().strip()
+        else:
+            bsddb_version = _("Unknown")
+        return {
+            _("Number of people"): self.get_number_of_people(),
+            _("Schema version"): schema_version,
+            _("Version"): bsddb_version,
+        }
+
 def mk_backup_name(database, base):
     """
     Return the backup name of the database table

From eeb150f1e3290b5df2968d589066f6d3d1c4d8dd Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Fri, 15 May 2015 10:48:14 -0400
Subject: [PATCH 075/105] Database API: expore name, full_name, and brief_name

---
 gramps/plugins/database/dictionarydb.py | 9 +++++++++
 gramps/plugins/database/djangodb.py     | 9 +++++++++
 2 files changed, 18 insertions(+)

diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 72a383469..ecdf5a112 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -404,6 +404,9 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.abort_possible = False
         self._bm_changes = 0
         self._directory = directory
+        self.full_name = None
+        self.path = None
+        self.brief_name = None
         if directory:
             self.load(directory)
 
@@ -1671,6 +1674,9 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         from gramps.plugins.importer.importxml import importData
         from gramps.cli.user import User 
         self._directory = directory
+        self.full_name = os.path.abspath(self._directory)
+        self.path = self.full_name
+        self.brief_name = os.path.basename(self._directory)
         filename = os.path.join(directory, "data.gramps")
         if os.path.isfile(filename):
             importData(self, filename, User())
@@ -1690,6 +1696,9 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def set_save_path(self, directory):
         self._directory = directory
+        self.full_name = os.path.abspath(self._directory)
+        self.path = self.full_name
+        self.brief_name = os.path.basename(self._directory)
 
     def undo(self, update_history=True):
         pass
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index 1c6e3a6a1..182295645 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -349,6 +349,9 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.abort_possible = False
         self._bm_changes = 0
         self._directory = directory
+        self.full_name = None
+        self.path = None
+        self.brief_name = None
         if directory:
             self.load(directory)
 
@@ -358,6 +361,9 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
              force_bsddb_downgrade=False,
              force_python_upgrade=False):
         self._directory = directory
+        self.full_name = os.path.abspath(self._directory)
+        self.path = self.full_name
+        self.brief_name = os.path.basename(self._directory)
         from django.conf import settings
         default_settings = {"__file__": 
                             os.path.join(directory, "default_settings.py")}
@@ -1836,6 +1842,9 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def set_save_path(self, directory):
         self._directory = directory
+        self.full_name = os.path.abspath(self._directory)
+        self.path = self.full_name
+        self.brief_name = os.path.basename(self._directory)
 
     ## Get types:
     def get_event_attribute_types(self):

From 014f8e61f1d7b0363b3c14eaa3657fdaba8de370 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Fri, 15 May 2015 22:50:54 -0400
Subject: [PATCH 076/105] DictionaryDb: reworked internal reprs; updated gender
 stats, researcher

---
 gramps/plugins/database/dictionarydb.py | 341 ++++++++++++++----------
 gramps/plugins/database/djangodb.py     |  17 +-
 2 files changed, 213 insertions(+), 145 deletions(-)

diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index ecdf5a112..38ad398ac 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -38,7 +38,8 @@ import logging
 #------------------------------------------------------------------------
 from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
-from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn, KEY_TO_NAME_MAP
+from gramps.gen.db import (DbReadBase, DbWriteBase, DbTxn, 
+                           KEY_TO_NAME_MAP, KEY_TO_CLASS_MAP)
 from gramps.gen.db.undoredo import DbUndo
 from gramps.gen.db.dbconst import *
 from gramps.gen.utils.callback import Callback
@@ -66,6 +67,7 @@ from gramps.gen.lib.place import Place
 from gramps.gen.lib.repo import Repository
 from gramps.gen.lib.note import Note
 from gramps.gen.lib.tag import Tag
+from gramps.gen.lib.genderstats import GenderStats
 
 _LOG = logging.getLogger(DBLOGNAME)
 
@@ -99,8 +101,8 @@ class Table(object):
         """
         return self.funcs["cursor_func"]()
 
-    def put(key, data, txn=None):
-        self[key] = data
+    def put(self, key, data, txn=None):
+        self.funcs["add_func"](data, txn)
 
 class Map(dict):
     """
@@ -137,15 +139,14 @@ class MetaCursor(object):
         pass
 
 class Cursor(object):
-    def __init__(self, map, func):
+    def __init__(self, map):
         self.map = map
-        self.func = func
         self._iter = self.__iter__()
     def __enter__(self):
         return self
     def __iter__(self):
         for item in self.map.keys():
-            yield (bytes(item, "utf-8"), self.func(item))
+            yield (bytes(item, "utf-8"), self.map[item])
     def __next__(self):
         try:
             return self._iter.__next__()
@@ -155,7 +156,7 @@ class Cursor(object):
         pass
     def iter(self):
         for item in self.map.keys():
-            yield (bytes(item, "utf-8"), self.func(item))
+            yield (bytes(item, "utf-8"), self.map[item])
     def first(self):
         self._iter = self.__iter__()
         try:
@@ -383,15 +384,25 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.nmap_index = 0
         self.env = Environment(self)
         self.person_map = Map(Table(self._tables["Person"]))
+        self.person_id_map = {}
         self.family_map = Map(Table(self._tables["Family"]))
+        self.family_id_map = {}
         self.place_map  = Map(Table(self._tables["Place"]))
+        self.place_id_map = {}
         self.citation_map = Map(Table(self._tables["Citation"]))
+        self.citation_id_map = {}
         self.source_map = Map(Table(self._tables["Source"]))
+        self.source_id_map = {}
         self.repository_map  = Map(Table(self._tables["Repository"]))
+        self.repository_id_map = {}
         self.note_map = Map(Table(self._tables["Note"]))
+        self.note_id_map = {}
         self.media_map  = Map(Table(self._tables["Media"]))
+        self.media_id_map = {}
         self.event_map  = Map(Table(self._tables["Event"]))
+        self.event_id_map = {}
         self.tag_map  = Map(Table(self._tables["Tag"]))
+        self.tag_id_map = {}
         self.metadata   = Map(Table({"cursor_func": lambda: MetaCursor()}))
         self.name_group = {}
         self.undo_callback = None
@@ -407,6 +418,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.full_name = None
         self.path = None
         self.brief_name = None
+        self.genderStats = GenderStats() # can pass in loaded stats as dict
+        self.owner = Researcher()
         if directory:
             self.load(directory)
 
@@ -425,12 +438,15 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return None
 
     def transaction_commit(self, txn):
+        ## FIXME
         pass
 
     def get_undodb(self):
+        ## FIXME
         return None
 
     def transaction_abort(self, txn):
+        ## FIXME
         pass
 
     @staticmethod
@@ -687,10 +703,12 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         ## Fixme: implement sort
         return self.person_map.keys()
 
-    def get_family_handles(self):
+    def get_family_handles(self, sort_handles=False):
+        ## Fixme: implement sort
         return self.family_map.keys()
 
     def get_event_handles(self, sort_handles=False):
+        ## Fixme: implement sort
         return self.event_map.keys()
 
     def get_citation_handles(self, sort_handles=False):
@@ -705,78 +723,80 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         ## Fixme: implement sort
         return self.place_map.keys()
 
-    def get_repository_handles(self):
+    def get_repository_handles(self, sort_handles=False):
+        ## Fixme: implement sort
         return self.repository_map.keys()
 
     def get_media_object_handles(self, sort_handles=False):
         ## Fixme: implement sort
         return self.media_map.keys()
 
-    def get_note_handles(self):
+    def get_note_handles(self, sort_handles=False):
+        ## Fixme: implement sort
         return self.note_map.keys()
 
     def get_tag_handles(self, sort_handles=False):
-        # FIXME: sort
+        # FIXME: implement sort
         return self.tag_map.keys()
 
     def get_event_from_handle(self, handle):
         event = None
         if handle in self.event_map:
-            event = self.event_map[handle]
+            event = Event.create(self.event_map[handle])
         return event
 
     def get_family_from_handle(self, handle): 
         family = None
         if handle in self.family_map:
-            family = self.family_map[handle]
+            family = Family.create(self.family_map[handle])
         return family
 
     def get_repository_from_handle(self, handle):
         repository = None
         if handle in self.repository_map:
-            repository = self.repository_map[handle]
+            repository = Repository.create(self.repository_map[handle])
         return repository
 
     def get_person_from_handle(self, handle):
         person = None
         if handle in self.person_map:
-            person = self.person_map[handle]
+            person = Person.create(self.person_map[handle])
         return person
 
     def get_place_from_handle(self, handle):
         place = None
         if handle in self.place_map:
-            place = self.place_map[handle]
+            place = Place.create(self.place_map[handle])
         return place
 
     def get_citation_from_handle(self, handle):
         citation = None
         if handle in self.citation_map:
-            citation = self.citation_map[handle]
+            citation = Citation.create(self.citation_map[handle])
         return citation
 
     def get_source_from_handle(self, handle):
         source = None
         if handle in self.source_map:
-            source = self.source_map[handle]
+            source = Source.create(self.source_map[handle])
         return source
 
     def get_note_from_handle(self, handle):
         note = None
         if handle in self.note_map:
-            note = self.note_map[handle]
+            note = Note.create(self.note_map[handle])
         return note
 
     def get_object_from_handle(self, handle):
         media = None
         if handle in self.media_map:
-            media = self.media_map[handle]
+            media = MediaObject.create(self.media_map[handle])
         return media
 
     def get_tag_from_handle(self, handle):
         tag = None
         if handle in self.tag_map:
-            tag = self.tag_map[handle]
+            tag = Tag.create(self.tag_map[handle])
         return tag
 
     def get_default_person(self):
@@ -787,81 +807,73 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             return None
 
     def iter_people(self):
-        return (person for person in self.person_map.values())
+        return (Person.create(person) for person in self.person_map.values())
 
     def iter_person_handles(self):
         return (handle for handle in self.person_map.keys())
 
     def iter_families(self):
-        return (family for family in self.family_map.values())
+        return (Family.create(family) for family in self.family_map.values())
 
     def iter_family_handles(self):
         return (handle for handle in self.family_map.keys())
 
     def get_tag_from_name(self, name):
-        for tag in self.tag_map.values():
+        ## Slow, but typically not too many tags:
+        for data in self.tag_map.values():
+            tag = Tag.create(data)
             if tag.name == name:
                 return tag
         return None
 
     def get_person_from_gramps_id(self, gramps_id):
-        for person in self.person_map.values():
-            if person.gramps_id == gramps_id:
-                return person
+        if gramps_id in self.person_id_map:
+            return Person.create(self.person_id_map[gramps_id])
         return None
 
     def get_family_from_gramps_id(self, gramps_id):
-        for family in self.family_map.values():
-            if family.gramps_id == gramps_id:
-                return family
+        if gramps_id in self.family_id_map:
+            return Family.create(self.family_id_map[gramps_id])
         return None
 
     def get_citation_from_gramps_id(self, gramps_id):
-        for citation in self.citation_map.values():
-            if citation.gramps_id == gramps_id:
-                return citation
+        if gramps_id in self.citation_id_map:
+            return Citation.create(self.citation_id_map[gramps_id])
         return None
 
     def get_source_from_gramps_id(self, gramps_id):
-        for source in self.source_map.values():
-            if source.gramps_id == gramps_id:
-                return source
+        if gramps_id in self.source_id_map:
+            return Source.create(self.source_id_map[gramps_id])
         return None
 
     def get_event_from_gramps_id(self, gramps_id):
-        for event in self.event_map.values():
-            if event.gramps_id == gramps_id:
-                return event
+        if gramps_id in self.event_id_map:
+            return Event.create(self.event_id_map[gramps_id])
         return None
 
     def get_media_from_gramps_id(self, gramps_id):
-        for media in self.media_map.values():
-            if media.gramps_id == gramps_id:
-                return media
+        if gramps_id in self.media_id_map:
+            return MediaObject.create(self.media_id_map[gramps_id])
         return None
 
     def get_place_from_gramps_id(self, gramps_id):
-        for place in self.place_map.values():
-            if place.gramps_id == gramps_id:
-                return place
+        if gramps_id in self.place_id_map:
+            return Place.create(self.place_id_map[gramps_id])
         return None
 
     def get_repository_from_gramps_id(self, gramps_id):
-        for repository in self.repository_map.values():
-            if repository.gramps_id == gramps_id:
-                return repository
+        if gramps_id in self.repository_id_map:
+            return Repository.create(self.repository_id_map[gramps_id])
         return None
 
     def get_note_from_gramps_id(self, gramps_id):
-        for note in self.note_map.values():
-            if note.gramps_id == gramps_id:
-                return note
+        if gramps_id in self.note_id_map:
+            return Note.create(self.note_id_map[gramps_id])
         return None
 
     def get_tag_from_gramps_id(self, gramps_id):
-        for tag in self.tag_map.values():
-            if tag.gramps_id == gramps_id:
-                return tag
+        if gramps_id in self.tag_id_map:
+            return Tag.create(self.tag_id_map[gramps_id])
         return None
 
     def get_number_of_people(self):
@@ -874,7 +886,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return len(self.place_map)
 
     def get_number_of_tags(self):
-        return 0 # FIXME
+        return len(self.tag_map)
 
     def get_number_of_families(self):
         return len(self.family_map)
@@ -895,52 +907,48 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return len(self.repository_map)
 
     def get_place_cursor(self):
-        return Cursor(self.place_map, self.get_raw_place_data)
+        return Cursor(self.place_map)
 
     def get_person_cursor(self):
-        return Cursor(self.person_map, self.get_raw_person_data)
+        return Cursor(self.person_map)
 
     def get_family_cursor(self):
-        return Cursor(self.family_map, self.get_raw_family_data)
+        return Cursor(self.family_map)
 
     def get_event_cursor(self):
-        return Cursor(self.event_map, self.get_raw_event_data)
+        return Cursor(self.event_map)
 
     def get_note_cursor(self):
-        return Cursor(self.note_map, self.get_raw_note_data)
+        return Cursor(self.note_map)
 
     def get_tag_cursor(self):
-        return Cursor(self.tag_map, self.get_raw_tag_data)
+        return Cursor(self.tag_map)
 
     def get_repository_cursor(self):
-        return Cursor(self.repository_map, self.get_raw_repository_data)
+        return Cursor(self.repository_map)
 
     def get_media_cursor(self):
-        return Cursor(self.media_map, self.get_raw_object_data)
+        return Cursor(self.media_map)
 
     def get_citation_cursor(self):
-        return Cursor(self.citation_map, self.get_raw_citation_data)
+        return Cursor(self.citation_map)
 
     def get_source_cursor(self):
-        return Cursor(self.source_map, self.get_raw_source_data)
+        return Cursor(self.source_map)
 
     def has_gramps_id(self, obj_key, gramps_id):
         key2table = {
-            PERSON_KEY:     self.person_map, 
-            FAMILY_KEY:     self.family_map, 
-            SOURCE_KEY:     self.source_map, 
-            CITATION_KEY:   self.citation_map, 
-            EVENT_KEY:      self.event_map, 
-            MEDIA_KEY:      self.media_map, 
-            PLACE_KEY:      self.place_map, 
-            REPOSITORY_KEY: self.repository_map, 
-            NOTE_KEY:       self.note_map, 
+            PERSON_KEY:     self.person_id_map, 
+            FAMILY_KEY:     self.family_id_map, 
+            SOURCE_KEY:     self.source_id_map, 
+            CITATION_KEY:   self.citation_id_map, 
+            EVENT_KEY:      self.event_id_map, 
+            MEDIA_KEY:      self.media_id_map, 
+            PLACE_KEY:      self.place_id_map, 
+            REPOSITORY_KEY: self.repository_id_map, 
+            NOTE_KEY:       self.note_id_map, 
             }
-        map = key2table[obj_key]
-        for item in map.values():
-            if item.gramps_id == gramps_id:
-                return True
-        return False
+        return gramps_id in key2table[obj_key]
 
     def has_person_handle(self, handle):
         return handle in self.person_map
@@ -981,59 +989,61 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         pass
 
     def set_default_person_handle(self, handle):
+        ## FIXME
         pass
 
     def set_mediapath(self, mediapath):
+        ## FIXME
         pass
 
     def get_raw_person_data(self, handle):
         if handle in self.person_map:
-            return self.person_map[handle].serialize()
+            return self.person_map[handle]
         return None
 
     def get_raw_family_data(self, handle):
         if handle in self.family_map:
-            return self.family_map[handle].serialize()
+            return self.family_map[handle]
         return None
 
     def get_raw_citation_data(self, handle):
         if handle in self.citation_map:
-            return self.citation_map[handle].serialize()
+            return self.citation_map[handle]
         return None
 
     def get_raw_source_data(self, handle):
         if handle in self.source_map:
-            return self.source_map[handle].serialize()
+            return self.source_map[handle]
         return None
 
     def get_raw_repository_data(self, handle):
         if handle in self.repository_map:
-            return self.repository_map[handle].serialize()
+            return self.repository_map[handle]
         return None
 
     def get_raw_note_data(self, handle):
         if handle in self.note_map:
-            return self.note_map[handle].serialize()
+            return self.note_map[handle]
         return None
 
     def get_raw_place_data(self, handle):
         if handle in self.place_map:
-            return self.place_map[handle].serialize()
+            return self.place_map[handle]
         return None
 
     def get_raw_object_data(self, handle):
         if handle in self.media_map:
-            return self.media_map[handle].serialize()
+            return self.media_map[handle]
         return None
 
     def get_raw_tag_data(self, handle):
         if handle in self.tag_map:
-            return self.tag_map[handle].serialize()
+            return self.tag_map[handle]
         return None
 
     def get_raw_event_data(self, handle):
         if handle in self.event_map:
-            return self.event_map[handle].serialize()
+            return self.event_map[handle]
         return None
 
     def add_person(self, person, trans, set_gid=True):
@@ -1127,7 +1137,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "person-update"
             else:
                 emit = "person-add"
-        self.person_map[person.handle] = person
+        self.person_map[person.handle] = person.serialize()
+        self.person_id_map[person.gramps_id] = self.person_map[person.handle]
         # Emit after added:
         if emit:
             self.emit(emit, ([person.handle],))
@@ -1139,7 +1150,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "family-update"
             else:
                 emit = "family-add"
-        self.family_map[family.handle] = family
+        self.family_map[family.handle] = family.serialize()
+        self.family_id_map[family.gramps_id] = self.family_map[family.handle]
         # Emit after added:
         if emit:
             self.emit(emit, ([family.handle],))
@@ -1151,7 +1163,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "citation-update"
             else:
                 emit = "citation-add"
-        self.citation_map[citation.handle] = citation
+        self.citation_map[citation.handle] = citation.serialize()
+        self.citation_id_map[citation.gramps_id] = self.citation_map[citation.handle]
         # Emit after added:
         if emit:
             self.emit(emit, ([citation.handle],))
@@ -1163,7 +1176,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "source-update"
             else:
                 emit = "source-add"
-        self.source_map[source.handle] = source
+        self.source_map[source.handle] = source.serialize()
+        self.source_id_map[source.gramps_id] = self.source_map[source.handle]
         # Emit after added:
         if emit:
             self.emit(emit, ([source.handle],))
@@ -1175,7 +1189,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "repository-update"
             else:
                 emit = "repository-add"
-        self.repository_map[repository.handle] = repository
+        self.repository_map[repository.handle] = repository.serialize()
+        self.repository_id_map[repository.gramps_id] = self.repository_map[repository.handle]
         # Emit after added:
         if emit:
             self.emit(emit, ([repository.handle],))
@@ -1187,7 +1202,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "note-update"
             else:
                 emit = "note-add"
-        self.note_map[note.handle] = note
+        self.note_map[note.handle] = note.serialize()
+        self.note_id_map[note.gramps_id] = self.note_map[note.handle]
         # Emit after added:
         if emit:
             self.emit(emit, ([note.handle],))
@@ -1199,7 +1215,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "place-update"
             else:
                 emit = "place-add"
-        self.place_map[place.handle] = place
+        self.place_map[place.handle] = place.serialize()
+        self.place_id_map[place.gramps_id] = self.place_map[place.handle]
         # Emit after added:
         if emit:
             self.emit(emit, ([place.handle],))
@@ -1211,7 +1228,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "event-update"
             else:
                 emit = "event-add"
-        self.event_map[event.handle] = event
+        self.event_map[event.handle] = event.serialize()
+        self.event_id_map[event.gramps_id] = self.event_map[event.handle]
         # Emit after added:
         if emit:
             self.emit(emit, ([event.handle],))
@@ -1223,7 +1241,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "tag-update"
             else:
                 emit = "tag-add"
-        self.tag_map[tag.handle] = tag
+        self.tag_map[tag.handle] = tag.serialize()
+        self.tag_id_map[tag.gramps_id] = self.tag_map[tag.handle]
         # Emit after added:
         if emit:
             self.emit(emit, ([tag.handle],))
@@ -1235,34 +1254,35 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "media-update"
             else:
                 emit = "media-add"
-        self.media_map[media.handle] = media
+        self.media_map[media.handle] = media.serialize()
+        self.media_id_map[media.gramps_id] = self.media_map[media.handle]
         # Emit after added:
         if emit:
             self.emit(emit, ([media.handle],))
 
     def get_gramps_ids(self, obj_key):
         key2table = {
-            PERSON_KEY:     self.person_map, 
-            FAMILY_KEY:     self.family_map, 
-            CITATION_KEY:   self.citation_map, 
-            SOURCE_KEY:     self.source_map, 
-            EVENT_KEY:      self.event_map, 
-            MEDIA_KEY:      self.media_map, 
-            PLACE_KEY:      self.place_map, 
-            REPOSITORY_KEY: self.repository_map, 
-            NOTE_KEY:       self.note_map, 
+            PERSON_KEY:     self.person_id_map, 
+            FAMILY_KEY:     self.family_id_map, 
+            CITATION_KEY:   self.citation_id_map, 
+            SOURCE_KEY:     self.source_id_map, 
+            EVENT_KEY:      self.event_id_map, 
+            MEDIA_KEY:      self.media_id_map, 
+            PLACE_KEY:      self.place_id_map, 
+            REPOSITORY_KEY: self.repository_id_map, 
+            NOTE_KEY:       self.note_id_map, 
             }
-        table = key2table[obj_key]
-        return [item.gramps_id for item in table.values()]
+        return list(key2table[obj_key].keys())
 
     def transaction_begin(self, transaction):
+        ## FIXME
         return 
 
-    def disable_signals(self):
-        pass
-
     def set_researcher(self, owner):
-        pass
+        self.owner.set_from(owner)
+
+    def get_researcher(self):
+        return self.owner
 
     def request_rebuild(self):
         self.emit('person-rebuild')
@@ -1332,7 +1352,9 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if self.readonly or not handle:
             return
         if handle in self.person_map:
+            person = Person.create(self.person_map[handle])
             del self.person_map[handle]
+            del self.person_id_map[person.gramps_id]
             self.emit("person-delete", ([handle],))
 
     def remove_source(self, handle, transaction):
@@ -1341,7 +1363,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         database, preserving the change in the passed transaction. 
         """
         self.__do_remove(handle, transaction, self.source_map, 
-                              SOURCE_KEY)
+                         self.source_id_map, SOURCE_KEY)
 
     def remove_citation(self, handle, transaction):
         """
@@ -1349,7 +1371,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         database, preserving the change in the passed transaction. 
         """
         self.__do_remove(handle, transaction, self.citation_map, 
-                              CITATION_KEY)
+                         self.citation_id_map, CITATION_KEY)
 
     def remove_event(self, handle, transaction):
         """
@@ -1357,7 +1379,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         database, preserving the change in the passed transaction. 
         """
         self.__do_remove(handle, transaction, self.event_map, 
-                              EVENT_KEY)
+                         self.event_id_map, EVENT_KEY)
 
     def remove_object(self, handle, transaction):
         """
@@ -1365,7 +1387,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         database, preserving the change in the passed transaction. 
         """
         self.__do_remove(handle, transaction, self.media_map, 
-                              MEDIA_KEY)
+                         self.media_id_map, MEDIA_KEY)
 
     def remove_place(self, handle, transaction):
         """
@@ -1373,7 +1395,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         database, preserving the change in the passed transaction. 
         """
         self.__do_remove(handle, transaction, self.place_map, 
-                              PLACE_KEY)
+                         self.place_id_map, PLACE_KEY)
 
     def remove_family(self, handle, transaction):
         """
@@ -1381,7 +1403,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         database, preserving the change in the passed transaction. 
         """
         self.__do_remove(handle, transaction, self.family_map, 
-                              FAMILY_KEY)
+                         self.family_id_map, FAMILY_KEY)
 
     def remove_repository(self, handle, transaction):
         """
@@ -1389,7 +1411,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         database, preserving the change in the passed transaction. 
         """
         self.__do_remove(handle, transaction, self.repository_map, 
-                              REPOSITORY_KEY)
+                         self.repository_id_map, REPOSITORY_KEY)
 
     def remove_note(self, handle, transaction):
         """
@@ -1397,7 +1419,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         database, preserving the change in the passed transaction. 
         """
         self.__do_remove(handle, transaction, self.note_map, 
-                              NOTE_KEY)
+                         self.note_id_map, NOTE_KEY)
 
     def remove_tag(self, handle, transaction):
         """
@@ -1405,7 +1427,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         database, preserving the change in the passed transaction. 
         """
         self.__do_remove(handle, transaction, self.tag_map, 
-                              TAG_KEY)
+                         self.tag_id_map, TAG_KEY)
 
     def is_empty(self):
         """
@@ -1416,13 +1438,13 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 return False
         return True
 
-    def __do_remove(self, handle, transaction, data_map, key):
+    def __do_remove(self, handle, transaction, data_map, data_id_map, key):
         if self.readonly or not handle:
             return
-        if isinstance(handle, str):
-            handle = handle.encode('utf-8')
         if handle in data_map:
+            obj = self._tables[KEY_TO_CLASS_MAP[key]]["class_func"].create(data_map[handle])
             del data_map[handle]
+            del data_id_map[obj.gramps_id]
             self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],))
 
     def delete_primary_from_reference_map(self, handle, transaction, txn=None):
@@ -1486,6 +1508,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     ## Missing:
 
     def backup(self):
+        ## FIXME
         pass
 
     def close(self):
@@ -1499,6 +1522,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             touch(filename)
 
     def find_backlink_handles(self, handle, include_classes=None):
+        ## FIXME
         return []
 
     def find_initial_person(self):
@@ -1508,22 +1532,30 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return None
 
     def find_place_child_handles(self, handle):
+        ## FIXME
         return []
 
     def get_bookmarks(self):
         return self.bookmarks
 
     def get_child_reference_types(self):
+        ## FIXME
         return []
 
     def get_citation_bookmarks(self):
         return self.citation_bookmarks
 
     def get_cursor(self, table, txn=None, update=False, commit=False):
+        ## FIXME
+        ## called from a complete find_back_ref
         pass
 
-    def get_dbname(self):
-        return "DictionaryDb"
+    # cursors for lookups in the reference_map for back reference
+    # lookups. The reference_map has three indexes:
+    # the main index: a tuple of (primary_handle, referenced_handle)
+    # the primary_handle index: the primary_handle
+    # the referenced_handle index: the referenced_handle
+    # the main index is unique, the others allow duplicate entries.
 
     def get_default_handle(self):
         items = self.person_map.keys()
@@ -1532,124 +1564,141 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return None
 
     def get_event_attribute_types(self):
+        ## FIXME
         return []
 
     def get_event_bookmarks(self):
         return self.event_bookmarks
 
     def get_event_roles(self):
+        ## FIXME
         return []
 
     def get_event_types(self):
+        ## FIXME
         return []
 
     def get_family_attribute_types(self):
+        ## FIXME
         return []
 
     def get_family_bookmarks(self):
         return self.family_bookmarks
 
     def get_family_event_types(self):
+        ## FIXME
         return []
 
     def get_family_relation_types(self):
+        ## FIXME
         return []
 
     def get_media_attribute_types(self):
+        ## FIXME
         return []
 
     def get_media_bookmarks(self):
         return self.media_bookmarks
 
     def get_name_types(self):
+        ## FIXME
         return []
 
     def get_note_bookmarks(self):
         return self.note_bookmarks
 
     def get_note_types(self):
+        ## FIXME
         return []
 
-    def get_number_of_records(self, table):
-        return 0
-
     def get_origin_types(self):
+        ## FIXME
         return []
 
     def get_person_attribute_types(self):
+        ## FIXME
         return []
 
     def get_person_event_types(self):
+        ## FIXME
         return []
 
     def get_place_bookmarks(self):
         return self.place_bookmarks
 
     def get_place_tree_cursor(self):
+        ## FIXME
         return []
 
     def get_place_types(self):
+        ## FIXME
         return []
 
     def get_repo_bookmarks(self):
         return self.repo_bookmarks
 
     def get_repository_types(self):
+        ## FIXME
         return []
 
     def get_save_path(self):
         return self._directory
 
     def get_source_attribute_types(self):
+        ## FIXME
         return []
 
     def get_source_bookmarks(self):
         return self.source_bookmarks
 
     def get_source_media_types(self):
+        ## FIXME
         return []
 
     def get_surname_list(self):
+        ## FIXME
         return []
 
     def get_url_types(self):
+        ## FIXME
         return []
 
     def has_changed(self):
+        ## FIXME
         return True
 
     def is_open(self):
-        return True
+        return self._directory is not None
 
     def iter_citation_handles(self):
         return (key for key in self.citation_map.keys())
 
     def iter_citations(self):
-        return (key for key in self.citation_map.values())
+        return (Citation.create(key) for key in self.citation_map.values())
 
     def iter_event_handles(self):
         return (key for key in self.event_map.keys())
 
     def iter_events(self):
-        return (key for key in self.event_map.values())
+        return (Events.create(key) for key in self.event_map.values())
 
     def iter_media_objects(self):
-        return (key for key in self.media_map.values())
+        return (MediaObject.create(key) for key in self.media_map.values())
 
     def iter_note_handles(self):
         return (key for key in self.note_map.keys())
 
     def iter_notes(self):
-        return (key for key in self.note_map.values())
+        return (Note.create(key) for key in self.note_map.values())
 
     def iter_place_handles(self):
         return (key for key in self.place_map.keys())
 
     def iter_places(self):
-        return (key for key in self.place_map.values())
+        return (Place.create(key) for key in self.place_map.values())
 
     def iter_repositories(self):
-        return (key for key in self.repositories_map.values())
+        return (Repository.create(key) for key in self.repositories_map.values())
 
     def iter_repository_handles(self):
         return (key for key in self.repositories_map.keys())
@@ -1658,13 +1707,13 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return (key for key in self.source_map.keys())
 
     def iter_sources(self):
-        return (key for key in self.source_map.values())
+        return (Source.create(key) for key in self.source_map.values())
 
     def iter_tag_handles(self):
         return (key for key in self.tag_map.keys())
 
     def iter_tags(self):
-        return (key for key in self.tag_map.values())
+        return (Tag.create(key) for key in self.tag_map.values())
 
     def load(self, directory, pulse_progress=None, mode=None, 
              force_schema_upgrade=False, 
@@ -1682,16 +1731,20 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             importData(self, filename, User())
 
     def prepare_import(self):
+        ## FIXME
         pass
 
     def redo(self, update_history=True):
+        ## FIXME
         pass
 
     def restore(self):
+        ## FIXME
         pass
 
     def set_prefixes(self, person, media, family, source, citation, 
                      place, event, repository, note):
+        ## FIXME
         pass
 
     def set_save_path(self, directory):
@@ -1701,6 +1754,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.brief_name = os.path.basename(self._directory)
 
     def undo(self, update_history=True):
+        ## FIXME
         pass
 
     def write_version(self, directory):
@@ -1749,3 +1803,10 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             name = None
         return name
 
+    def reindex_reference_map(self):
+        ## FIXME
+        pass
+
+    def rebuild_secondary(self, update):
+        ## FIXME
+        pass
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index 182295645..f0d1faf44 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -44,7 +44,7 @@ from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
 from gramps.gen.lib import (Person, Family, Event, Place, Repository, 
                             Citation, Source, Note, MediaObject, Tag, 
-                            Researcher)
+                            Researcher, GenderStats)
 from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn
 from gramps.gen.db.undoredo import DbUndo
 from gramps.gen.utils.callback import Callback
@@ -352,6 +352,8 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.full_name = None
         self.path = None
         self.brief_name = None
+        self.genderStats = GenderStats() # can pass in loaded stats as dict
+        self.owner = Researcher()
         if directory:
             self.load(directory)
 
@@ -788,8 +790,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return None
 
     def get_researcher(self):
-        obj = Researcher()
-        return obj
+        return self.owner
 
     def get_tag_handles(self, sort_handles=False):
         if sort_handles:
@@ -1704,7 +1705,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return 
 
     def set_researcher(self, owner):
-        pass
+        self.owner.set_from(owner)
 
     def copy_from_db(self, db):
         """
@@ -2037,9 +2038,10 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def remove_from_surname_list(self, person):
         ## FIXME
+        ## called by a complete commit_person
         pass
     
-    ## missing
+    ## was missing
 
     def find_place_child_handles(self, handle):
         pass
@@ -2146,3 +2148,8 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             name = None
         return name
 
+    def reindex_reference_map(self):
+        pass
+
+    def rebuild_secondary(self, update):
+        pass

From b095c6b6061f0aa1f437606592cad1b8fa52dce3 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Fri, 15 May 2015 23:26:22 -0400
Subject: [PATCH 077/105] DjangoDb: always force a gramps_id; typo fix

---
 gramps/plugins/database/djangodb.py | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index f0d1faf44..ca55eb0b1 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -1429,7 +1429,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def add_person(self, person, trans, set_gid=True):
         if not person.handle:
             person.handle = create_id()
-        if not person.gramps_id and set_gid:
+        if not person.gramps_id:
             person.gramps_id = self.find_next_person_gramps_id()
         self.commit_person(person, trans)
         return person.handle
@@ -1437,7 +1437,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def add_family(self, family, trans, set_gid=True):
         if not family.handle:
             family.handle = create_id()
-        if not family.gramps_id and set_gid:
+        if not family.gramps_id:
             family.gramps_id = self.find_next_family_gramps_id()
         self.commit_family(family, trans)
         return family.handle
@@ -1445,7 +1445,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def add_citation(self, citation, trans, set_gid=True):
         if not citation.handle:
             citation.handle = create_id()
-        if not citation.gramps_id and set_gid:
+        if not citation.gramps_id:
             citation.gramps_id = self.find_next_citation_gramps_id()
         self.commit_citation(citation, trans)
         return citation.handle
@@ -1453,7 +1453,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def add_source(self, source, trans, set_gid=True):
         if not source.handle:
             source.handle = create_id()
-        if not source.gramps_id and set_gid:
+        if not source.gramps_id:
             source.gramps_id = self.find_next_source_gramps_id()
         self.commit_source(source, trans)
         return source.handle
@@ -1461,7 +1461,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def add_repository(self, repository, trans, set_gid=True):
         if not repository.handle:
             repository.handle = create_id()
-        if not repository.gramps_id and set_gid:
+        if not repository.gramps_id:
             repository.gramps_id = self.find_next_repository_gramps_id()
         self.commit_repository(repository, trans)
         return repository.handle
@@ -1469,7 +1469,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def add_note(self, note, trans, set_gid=True):
         if not note.handle:
             note.handle = create_id()
-        if not note.gramps_id and set_gid:
+        if not note.gramps_id:
             note.gramps_id = self.find_next_note_gramps_id()
         self.commit_note(note, trans)
         return note.handle
@@ -1477,7 +1477,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def add_place(self, place, trans, set_gid=True):
         if not place.handle:
             place.handle = create_id()
-        if not place.gramps_id and set_gid:
+        if not place.gramps_id:
             place.gramps_id = self.find_next_place_gramps_id()
         self.commit_place(place, trans)
         return place.handle
@@ -1485,7 +1485,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def add_event(self, event, trans, set_gid=True):
         if not event.handle:
             event.handle = create_id()
-        if not event.gramps_id and set_gid:
+        if not event.gramps_id:
             event.gramps_id = self.find_next_event_gramps_id()
         self.commit_event(event, trans)
         return event.handle
@@ -1493,7 +1493,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def add_tag(self, tag, trans):
         if not tag.handle:
             tag.handle = create_id()
-        self.commit_event(tag, trans)
+        self.commit_tag(tag, trans)
         return tag.handle
 
     def add_object(self, obj, transaction, set_gid=True):
@@ -1505,7 +1505,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         """
         if not obj.handle:
             obj.handle = create_id()
-        if not obj.gramps_id and set_gid:
+        if not obj.gramps_id:
             obj.gramps_id = self.find_next_object_gramps_id()
         self.commit_media_object(obj, transaction)
         return obj.handle
@@ -1664,7 +1664,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 else:
                     self.emit("tag-add", ([tag.handle],))
 
-    def commit_media_object(self, media, transaction, change_time=None):
+    def commit_media_object(self, media, trans, change_time=None):
         """
         Commit the specified MediaObject to the database, storing the changes
         as part of the transaction.

From 576db27e9de6ed812bf6c4e484a0b858ead6a2bb Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 16 May 2015 00:16:33 -0400
Subject: [PATCH 078/105] Importers: added db.prepare_import/db.commit_import
 to wrap imports

---
 gramps/plugins/database/bsddb_support/write.py | 12 ++++++++++++
 gramps/plugins/database/dictionarydb.py        | 13 +++++++++++++
 gramps/plugins/database/djangodb.py            |  2 +-
 gramps/plugins/importer/importcsv.py           |  2 ++
 gramps/plugins/importer/importgedcom.py        |  2 ++
 gramps/plugins/importer/importgeneweb.py       |  2 ++
 gramps/plugins/importer/importgpkg.py          |  2 ++
 gramps/plugins/importer/importprogen.py        |  2 ++
 gramps/plugins/importer/importvcard.py         |  2 ++
 gramps/plugins/importer/importxml.py           |  2 ++
 gramps/webapp/reports.py                       |  4 ++--
 11 files changed, 42 insertions(+), 3 deletions(-)

diff --git a/gramps/plugins/database/bsddb_support/write.py b/gramps/plugins/database/bsddb_support/write.py
index 46430ee85..65bf71980 100644
--- a/gramps/plugins/database/bsddb_support/write.py
+++ b/gramps/plugins/database/bsddb_support/write.py
@@ -2472,6 +2472,18 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
             _("Version"): bsddb_version,
         }
 
+    def prepare_import(self):
+        """
+        Initialization before imports
+        """
+        pass
+
+    def commit_import(self):
+        """
+        Post process after imports
+        """
+        pass
+
 def mk_backup_name(database, base):
     """
     Return the backup name of the database table
diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 38ad398ac..44e3721fa 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -1810,3 +1810,16 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def rebuild_secondary(self, update):
         ## FIXME
         pass
+
+    def prepare_import(self):
+        """
+        Initialization before imports
+        """
+        pass
+
+    def commit_import(self):
+        """
+        Post process after imports
+        """
+        pass
+
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index ca55eb0b1..f7fe47892 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -518,7 +518,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 self.dji.add_tag_detail(obj.serialize())
         self.use_import_cache = False
         self.import_cache = {}
-        self.dji.update_publics()
+        self.request_rebuild()
 
     def transaction_commit(self, txn):
         pass
diff --git a/gramps/plugins/importer/importcsv.py b/gramps/plugins/importer/importcsv.py
index 4c1e61764..44f7d29ed 100644
--- a/gramps/plugins/importer/importcsv.py
+++ b/gramps/plugins/importer/importcsv.py
@@ -103,8 +103,10 @@ def importData(dbase, filename, user):
         parser = CSVParser(dbase, user, (config.get('preferences.tag-on-import-format') if 
                                          config.get('preferences.tag-on-import') else None))
     try:
+        dbase.prepare_import()
         with open(filename, 'r') as filehandle:
             parser.parse(filehandle)
+        dbase.commit_import()
     except EnvironmentError as err:
         user.notify_error(_("%s could not be opened\n") % filename, str(err))
         return
diff --git a/gramps/plugins/importer/importgedcom.py b/gramps/plugins/importer/importgedcom.py
index 5f095674a..34a1d4876 100644
--- a/gramps/plugins/importer/importgedcom.py
+++ b/gramps/plugins/importer/importgedcom.py
@@ -131,7 +131,9 @@ def importData(database, filename, user):
     try:
         read_only = database.readonly
         database.readonly = False
+        database.prepare_import()
         gedparse.parse_gedcom_file(False)
+        database.commit_import()
         database.readonly = read_only
         ifile.close()
     except IOError as msg:
diff --git a/gramps/plugins/importer/importgeneweb.py b/gramps/plugins/importer/importgeneweb.py
index 1ad57fc16..5147ebc57 100644
--- a/gramps/plugins/importer/importgeneweb.py
+++ b/gramps/plugins/importer/importgeneweb.py
@@ -87,7 +87,9 @@ def importData(database, filename, user):
         return
 
     try:
+        database.prepare_import()
         status = g.parse_geneweb_file()
+        database.commit_import()
     except IOError as msg:
         errmsg = _("%s could not be opened\n") % filename
         user.notify_error(errmsg,str(msg))
diff --git a/gramps/plugins/importer/importgpkg.py b/gramps/plugins/importer/importgpkg.py
index 3c7f1b4c7..2a164b09d 100644
--- a/gramps/plugins/importer/importgpkg.py
+++ b/gramps/plugins/importer/importgpkg.py
@@ -93,7 +93,9 @@ def impData(database, name, user):
     imp_db_name = os.path.join(tmpdir_path, XMLFILE)  
 
     importer = importData
+    database.prepare_import()
     info = importer(database, imp_db_name, user)
+    database.commit_import()
 
     newmediapath = database.get_mediapath()
     #import of gpkg should not change media path as all media has new paths!
diff --git a/gramps/plugins/importer/importprogen.py b/gramps/plugins/importer/importprogen.py
index 16b72eeea..3ad119660 100644
--- a/gramps/plugins/importer/importprogen.py
+++ b/gramps/plugins/importer/importprogen.py
@@ -75,7 +75,9 @@ def _importData(database, filename, user):
         return
 
     try:
+        database.prepare_import()
         status = g.parse_progen_file()
+        database.commit_import()
     except ProgenError as msg:
         user.notify_error(_("Pro-Gen data error"), str(msg))
         return
diff --git a/gramps/plugins/importer/importvcard.py b/gramps/plugins/importer/importvcard.py
index 9495afb5f..e9f42d44b 100644
--- a/gramps/plugins/importer/importvcard.py
+++ b/gramps/plugins/importer/importvcard.py
@@ -63,8 +63,10 @@ def importData(database, filename, user):
     """Function called by Gramps to import data on persons in VCard format."""
     parser = VCardParser(database)
     try:
+        database.prepare_import()
         with OpenFileOrStdin(filename) as filehandle:
             parser.parse(filehandle)
+        database.commit_import()
     except EnvironmentError as msg:
         user.notify_error(_("%s could not be opened\n") % filename, str(msg))
         return
diff --git a/gramps/plugins/importer/importxml.py b/gramps/plugins/importer/importxml.py
index 3cbea0785..acee74f69 100644
--- a/gramps/plugins/importer/importxml.py
+++ b/gramps/plugins/importer/importxml.py
@@ -122,6 +122,7 @@ def importData(database, filename, user):
     line_cnt = 0
     person_cnt = 0
     
+    database.prepare_import()
     with ImportOpenFileContextManager(filename, user) as xml_file:
         if xml_file is None:
             return
@@ -162,6 +163,7 @@ def importData(database, filename, user):
                           "valid Gramps database."))
             return
 
+    database.commit_import()
     database.readonly = read_only
     return info
 
diff --git a/gramps/webapp/reports.py b/gramps/webapp/reports.py
index c0c7f480e..35cc66356 100644
--- a/gramps/webapp/reports.py
+++ b/gramps/webapp/reports.py
@@ -85,9 +85,9 @@ def import_file(db, filename, user):
                     print("ERROR:", name, exception)
                 return False
             import_function = getattr(mod, pdata.import_function)
-            db.prepare_import()
+            #db.prepare_import()
             retval = import_function(db, filename, user)
-            db.commit_import()
+            #db.commit_import()
             return retval
     return False
 

From cfb33ec1a23cfc21c3a69eba50914b6d06c89c79 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 16 May 2015 00:46:22 -0400
Subject: [PATCH 079/105] DjangoDb: typo, added logger

---
 gramps/gen/dbstate.py               | 11 +++++++++++
 gramps/plugins/database/djangodb.py |  2 +-
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py
index 0d9c30d4a..85722464d 100644
--- a/gramps/gen/dbstate.py
+++ b/gramps/gen/dbstate.py
@@ -29,6 +29,14 @@ from .proxy.proxybase import ProxyDbBase
 from .utils.callback import Callback
 from .config import config
 
+#-------------------------------------------------------------------------
+#
+# set up logging
+#
+#-------------------------------------------------------------------------
+import logging
+LOG = logging.getLogger(".dbstate")
+
 class DbState(Callback):
     """
     Provide a class to encapsulate the state of the database.
@@ -152,6 +160,7 @@ class DbState(Callback):
 
     ## Work-around for databases that need sys refresh (django):
     def modules_is_set(self):
+        LOG.warn("modules_is_set?")
         if hasattr(self, "_modules"):
             return self._modules != None
         else:
@@ -159,6 +168,7 @@ class DbState(Callback):
             return False
 
     def reset_modules(self):
+        LOG.warn("reset_modules!")
         # First, clear out old modules:
         for key in list(sys.modules.keys()):
             del(sys.modules[key])
@@ -167,5 +177,6 @@ class DbState(Callback):
             sys.modules[key] = self._modules[key]
 
     def save_modules(self):
+        LOG.warn("save_modules!")
         self._modules = sys.modules.copy()
 
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index f7fe47892..c28cc9265 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -1670,7 +1670,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         as part of the transaction.
         """
         if self.use_import_cache:
-            self.import_cache[obj.handle] = media
+            self.import_cache[media.handle] = media
         else:
             raw = media.serialize()
             items = self.dji.Media.filter(handle=media.handle)

From 4805c3c7f0f5a76640be69d2d070747fc2e99b44 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 16 May 2015 01:40:05 -0400
Subject: [PATCH 080/105] DjangoDb: force load when write_version/create to
 make work with reset modules

---
 gramps/gen/dbstate.py               | 6 +++---
 gramps/plugins/database/djangodb.py | 6 ++++++
 2 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py
index 85722464d..9d1ce8071 100644
--- a/gramps/gen/dbstate.py
+++ b/gramps/gen/dbstate.py
@@ -160,7 +160,7 @@ class DbState(Callback):
 
     ## Work-around for databases that need sys refresh (django):
     def modules_is_set(self):
-        LOG.warn("modules_is_set?")
+        LOG.info("modules_is_set?")
         if hasattr(self, "_modules"):
             return self._modules != None
         else:
@@ -168,7 +168,7 @@ class DbState(Callback):
             return False
 
     def reset_modules(self):
-        LOG.warn("reset_modules!")
+        LOG.info("reset_modules!")
         # First, clear out old modules:
         for key in list(sys.modules.keys()):
             del(sys.modules[key])
@@ -177,6 +177,6 @@ class DbState(Callback):
             sys.modules[key] = self._modules[key]
 
     def save_modules(self):
-        LOG.warn("save_modules!")
+        LOG.info("save_modules!")
         self._modules = sys.modules.copy()
 
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index c28cc9265..cb8f70238 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -362,6 +362,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
              force_bsddb_upgrade=False,
              force_bsddb_downgrade=False,
              force_python_upgrade=False):
+        _LOG.info("Django loading...")
         self._directory = directory
         self.full_name = os.path.abspath(self._directory)
         self.path = self.full_name
@@ -380,9 +381,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             def __getattr__(self, item):
                 return self.dictionary[item]
 
+        _LOG.info("Django loading defaults from: " + directory)
         try:
             settings.configure(Module(default_settings))
         except RuntimeError:
+            _LOG.info("Django already configured error! Shouldn't happen!")
             # already configured; ignore
             pass
 
@@ -455,6 +458,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.url_types = set()
         self.media_attributes = set()
         self.place_types = set()
+        _LOG.info("Django loading... done!")
 
     def prepare_import(self):
         """
@@ -2108,6 +2112,8 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         for filename in os.listdir(defaults):
             fullpath = os.path.abspath(os.path.join(defaults, filename))
             shutil.copy2(fullpath, directory)
+        # force load, to get all modules loaded because of reset issue
+        self.load(directory)
 
     def report_bm_change(self):
         """

From 8a15aaffb700ac14a1846f6c224aa1b16641cc82 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 16 May 2015 02:15:02 -0400
Subject: [PATCH 081/105] Added missing function; allow failed plugin message
 to show

---
 gramps/gen/db/__init__.py | 21 +++++++++++++++++++++
 gramps/gui/viewmanager.py |  7 ++++++-
 2 files changed, 27 insertions(+), 1 deletion(-)

diff --git a/gramps/gen/db/__init__.py b/gramps/gen/db/__init__.py
index 066571e38..10f751303 100644
--- a/gramps/gen/db/__init__.py
+++ b/gramps/gen/db/__init__.py
@@ -88,3 +88,24 @@ from .base import *
 from .dbconst import *
 from .txn import *
 from .exceptions import *
+
+def find_surname_name(key, data):
+    """
+    Creating a surname from raw name, to use for sort and index
+    returns a byte string
+    """
+    return __index_surname(data[5])
+
+def __index_surname(surn_list):
+    """
+    All non pa/matronymic surnames are used in indexing.
+    pa/matronymic not as they change for every generation!
+    returns a byte string
+    """
+    from gramps.gen.lib import NameOriginType
+    if surn_list:
+        surn = " ".join([x[0] for x in surn_list if not (x[3][0] in [
+                    NameOriginType.PATRONYMIC, NameOriginType.MATRONYMIC]) ])
+    else:
+        surn = ""
+    return surn
diff --git a/gramps/gui/viewmanager.py b/gramps/gui/viewmanager.py
index cb2be6f16..11e37a1c9 100644
--- a/gramps/gui/viewmanager.py
+++ b/gramps/gui/viewmanager.py
@@ -1593,6 +1593,11 @@ def run_plugin(pdata, dbstate, uistate):
     mod = pmgr.load_plugin(pdata)
     if not mod:
         #import of plugin failed
+        failed = pmgr.get_fail_list()
+        if failed:
+            error_msg = failed[-1][1][1]
+        else:
+            error_msg = "(no error message)"
         ErrorDialog(
             _('Failed Loading Plugin'),
             _('The plugin %(name)s did not load and reported an error.\n\n'
@@ -1607,7 +1612,7 @@ def run_plugin(pdata, dbstate, uistate):
                 'gramps_bugtracker_url' : URL_BUGHOME,
                 'firstauthoremail': pdata.authors_email[0] if
                         pdata.authors_email else '...',
-                'error_msg': pmgr.get_fail_list()[-1][1][1]})
+                  'error_msg': error_msg})
         return
 
     if pdata.ptype == REPORT:

From 97fd387c346b5fba623988ef8a95d53070154d38 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 16 May 2015 09:18:41 -0400
Subject: [PATCH 082/105] Removed duplicate methods

---
 gramps/plugins/database/dictionarydb.py |  8 --------
 gramps/plugins/database/djangodb.py     | 21 +++++----------------
 2 files changed, 5 insertions(+), 24 deletions(-)

diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 44e3721fa..878afa880 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -695,10 +695,6 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def get_name_group_mapping(self, key):
         return None
 
-    def get_researcher(self):
-        obj = Researcher()
-        return obj
-
     def get_person_handles(self, sort_handles=False):
         ## Fixme: implement sort
         return self.person_map.keys()
@@ -1730,10 +1726,6 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if os.path.isfile(filename):
             importData(self, filename, User())
 
-    def prepare_import(self):
-        ## FIXME
-        pass
-
     def redo(self, update_history=True):
         ## FIXME
         pass
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index cb8f70238..0091d033a 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -96,8 +96,10 @@ class Table(object):
         """
         return self.funcs["cursor_func"]()
 
-    def put(key, data, txn=None):
-        self[key] = data
+    def put(self, key, data, txn=None):
+        ## FIXME: probably needs implementing?
+        #self[key] = data
+        pass
 
 class Map(dict):
     """
@@ -196,6 +198,7 @@ class DjangoTxn(DbTxn):
     def put(self, handle, new_data, txn):
         """
         """
+        ## FIXME: probably not correct?
         txn[handle] = new_data
 
     def commit(self):
@@ -2053,20 +2056,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def get_cursor(self, table, txn=None, update=False, commit=False):
         pass
 
-    def get_from_name_and_handle(self, table_name, handle):
-        """
-        Returns a gen.lib object (or None) given table_name and
-        handle.
-
-        Examples:
-
-        >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008")
-        >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23")
-        """
-        if table_name in self._tables:
-            return self._tables[table_name]["handle_func"](handle)
-        return None
-
     def get_number_of_records(self, table):
         pass
 

From 8d3657f54aa14a082882cfc3b4bd8226c935d6da Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sun, 17 May 2015 11:38:31 -0400
Subject: [PATCH 083/105] Diff: fixed import of DictionaryDb; removed mistaken
 tag.gramps_id references in DictionartDb

---
 gramps/gen/merge/diff.py                |  2 +-
 gramps/plugins/database/dictionarydb.py | 12 +++---------
 2 files changed, 4 insertions(+), 10 deletions(-)

diff --git a/gramps/gen/merge/diff.py b/gramps/gen/merge/diff.py
index 1ba5a45f7..0993638c3 100644
--- a/gramps/gen/merge/diff.py
+++ b/gramps/gen/merge/diff.py
@@ -28,7 +28,7 @@ from gramps.cli.user import User
 from ..dbstate import DbState
 from gramps.cli.grampscli import CLIManager
 from ..plug import BasePluginManager
-from ..db.dictionary import DictionaryDb
+from gramps.plugins.database.dictionarydb import DictionaryDb
 from gramps.gen.lib.handle import HandleClass, Handle
 from gramps.gen.lib import *
 from gramps.gen.lib.personref import PersonRef
diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 878afa880..549732b34 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -402,7 +402,6 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.event_map  = Map(Table(self._tables["Event"]))
         self.event_id_map = {}
         self.tag_map  = Map(Table(self._tables["Tag"]))
-        self.tag_id_map = {}
         self.metadata   = Map(Table({"cursor_func": lambda: MetaCursor()}))
         self.name_group = {}
         self.undo_callback = None
@@ -867,11 +866,6 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             return Note.create(self.note_id_map[gramps_id])
         return None
 
-    def get_tag_from_gramps_id(self, gramps_id):
-        if gramps_id in self.tag_id_map:
-            return Tag.create(self.tag_id_map[gramps_id])
-        return None
-
     def get_number_of_people(self):
         return len(self.person_map)
 
@@ -1238,7 +1232,6 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             else:
                 emit = "tag-add"
         self.tag_map[tag.handle] = tag.serialize()
-        self.tag_id_map[tag.gramps_id] = self.tag_map[tag.handle]
         # Emit after added:
         if emit:
             self.emit(emit, ([tag.handle],))
@@ -1423,7 +1416,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         database, preserving the change in the passed transaction. 
         """
         self.__do_remove(handle, transaction, self.tag_map, 
-                         self.tag_id_map, TAG_KEY)
+                         None, TAG_KEY)
 
     def is_empty(self):
         """
@@ -1440,7 +1433,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if handle in data_map:
             obj = self._tables[KEY_TO_CLASS_MAP[key]]["class_func"].create(data_map[handle])
             del data_map[handle]
-            del data_id_map[obj.gramps_id]
+            if data_id_map:
+                del data_id_map[obj.gramps_id]
             self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],))
 
     def delete_primary_from_reference_map(self, handle, transaction, txn=None):

From 8a657250b128a63755ca4797ec55aebeec582571 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sun, 17 May 2015 19:01:21 -0400
Subject: [PATCH 084/105] DictionaryDb: give handle in bytes, handle as str
 internally in dict

---
 gramps/plugins/database/dictionarydb.py | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 549732b34..2eda2460d 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -735,60 +735,80 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return self.tag_map.keys()
 
     def get_event_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
         event = None
         if handle in self.event_map:
             event = Event.create(self.event_map[handle])
         return event
 
     def get_family_from_handle(self, handle): 
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
         family = None
         if handle in self.family_map:
             family = Family.create(self.family_map[handle])
         return family
 
     def get_repository_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
         repository = None
         if handle in self.repository_map:
             repository = Repository.create(self.repository_map[handle])
         return repository
 
     def get_person_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
         person = None
         if handle in self.person_map:
             person = Person.create(self.person_map[handle])
         return person
 
     def get_place_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
         place = None
         if handle in self.place_map:
             place = Place.create(self.place_map[handle])
         return place
 
     def get_citation_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
         citation = None
         if handle in self.citation_map:
             citation = Citation.create(self.citation_map[handle])
         return citation
 
     def get_source_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
         source = None
         if handle in self.source_map:
             source = Source.create(self.source_map[handle])
         return source
 
     def get_note_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
         note = None
         if handle in self.note_map:
             note = Note.create(self.note_map[handle])
         return note
 
     def get_object_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
         media = None
         if handle in self.media_map:
             media = MediaObject.create(self.media_map[handle])
         return media
 
     def get_tag_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
         tag = None
         if handle in self.tag_map:
             tag = Tag.create(self.tag_map[handle])

From 8d92b5d63442ea4f3b200d3ee1d362d5f66664b4 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Thu, 21 May 2015 10:51:36 -0400
Subject: [PATCH 085/105] Added DbState.open_database() for opening without
 DbManager

---
 gramps/gen/dbstate.py | 56 ++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 53 insertions(+), 3 deletions(-)

diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py
index 9d1ce8071..b232d612e 100644
--- a/gramps/gen/dbstate.py
+++ b/gramps/gen/dbstate.py
@@ -23,6 +23,8 @@
 Provide the database state class
 """
 import sys
+import os
+import io
 
 from .db import DbReadBase
 from .proxy.proxybase import ProxyDbBase
@@ -62,9 +64,10 @@ class DbState(Callback):
         Closes the existing db, and opens a new one.
         Retained for backward compatibility.
         """
-        self.emit('no-database', ())
-        self.db.close()
-        self.change_database_noclose(database)
+        if database:
+            self.emit('no-database', ())
+            self.db.close()
+            self.change_database_noclose(database)
 
     def change_database_noclose(self, database):
         """
@@ -158,6 +161,53 @@ class DbState(Callback):
             database = getattr(mod, pdata.databaseclass)
             return database()
 
+    def open_database(self, dbname, force_unlock=False, callback=None):
+        """
+        Open a database by name and return the database.
+        """
+        data = self.lookup_family_tree(dbname)
+        database = None
+        if data:
+            dbpath, locked, locked_by, backend = data
+            if (not locked) or (locked and force_unlock):
+                database = self.make_database(backend)
+                database.load(dbpath, callback=callback)
+        return database
+
+    def lookup_family_tree(self, dbname):
+        """
+        Find a Family Tree given its name, and return properties.
+        """
+        dbdir = os.path.expanduser(config.get('behavior.database-path'))
+        for dpath in os.listdir(dbdir):
+            dirpath = os.path.join(dbdir, dpath)
+            path_name = os.path.join(dirpath, "name.txt")
+            if os.path.isfile(path_name):
+                file = io.open(path_name, 'r', encoding='utf8')
+                name = file.readline().strip()
+                file.close()
+                if dbname == name:
+                    locked = False
+                    locked_by = None
+                    backend = None
+                    fname = os.path.join(dirpath, "database.txt")
+                    if os.path.isfile(fname):
+                        ifile = io.open(fname, 'r', encoding='utf8')
+                        backend = ifile.read().strip()
+                        ifile.close()
+                    else:
+                        backend = "bsddb"
+                    try:
+                        fname = os.path.join(dirpath, "lock")
+                        ifile = io.open(fname, 'r', encoding='utf8')
+                        locked_by = ifile.read().strip()
+                        locked = True
+                        ifile.close()
+                    except (OSError, IOError):
+                        pass
+                    return (dirpath, locked, locked_by, backend)
+        return None
+
     ## Work-around for databases that need sys refresh (django):
     def modules_is_set(self):
         LOG.info("modules_is_set?")

From 8babc69dc5c30a6db37894a978edb520162ed604 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 23 May 2015 01:34:16 -0400
Subject: [PATCH 086/105] WIP: Added DB-API 2.0 interface; needs to load/save
 details from dbdir; currently using sqlite :memory: database. But could use
 any DB-API 2.0 compatible layers.

---
 gramps/plugins/database/dbapi.gpr.py    |   31 +
 gramps/plugins/database/dbapi.py        | 2231 +++++++++++++++++++++++
 gramps/plugins/database/dictionarydb.py |    2 +-
 3 files changed, 2263 insertions(+), 1 deletion(-)
 create mode 100644 gramps/plugins/database/dbapi.gpr.py
 create mode 100644 gramps/plugins/database/dbapi.py

diff --git a/gramps/plugins/database/dbapi.gpr.py b/gramps/plugins/database/dbapi.gpr.py
new file mode 100644
index 000000000..2749e7a50
--- /dev/null
+++ b/gramps/plugins/database/dbapi.gpr.py
@@ -0,0 +1,31 @@
+#
+# Gramps - a GTK+/GNOME based genealogy program
+#
+# Copyright (C) 2015 Douglas Blank <doug.blank@gmail.com>
+#
+# 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; either version 2 of the License, or
+# (at your option) any later version.
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+
+plg = newplugin()
+plg.id    = 'dbapi'
+plg.name  = _("DB-API 2.0")
+plg.name_accell  = _("DB-_API 2.0")
+plg.description =  _("DB-API 2.0 Database Backend")
+plg.version = '1.0'
+plg.gramps_target_version = "4.2"
+plg.status = STABLE
+plg.fname = 'dbapi.py'
+plg.ptype = DATABASE
+plg.databaseclass = 'DBAPI'
diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
new file mode 100644
index 000000000..b5d61e5dc
--- /dev/null
+++ b/gramps/plugins/database/dbapi.py
@@ -0,0 +1,2231 @@
+#------------------------------------------------------------------------
+#
+# Python Modules
+#
+#------------------------------------------------------------------------
+import pickle
+import base64
+import time
+import re
+import os
+import logging
+
+#------------------------------------------------------------------------
+#
+# Gramps Modules
+#
+#------------------------------------------------------------------------
+import gramps
+from gramps.gen.const import GRAMPS_LOCALE as glocale
+_ = glocale.translation.gettext
+from gramps.gen.db import (DbReadBase, DbWriteBase, DbTxn, 
+                           KEY_TO_NAME_MAP, KEY_TO_CLASS_MAP)
+from gramps.gen.utils.callback import Callback
+from gramps.gen.updatecallback import UpdateCallback
+from gramps.gen.db.undoredo import DbUndo
+from gramps.gen.db.dbconst import *
+from gramps.gen.db import (PERSON_KEY,
+                           FAMILY_KEY,
+                           CITATION_KEY,
+                           SOURCE_KEY,
+                           EVENT_KEY,
+                           MEDIA_KEY,
+                           PLACE_KEY,
+                           REPOSITORY_KEY,
+                           NOTE_KEY,
+                           TAG_KEY)
+
+from gramps.gen.utils.id import create_id
+from gramps.gen.lib.researcher import Researcher
+from gramps.gen.lib.mediaobj import MediaObject
+from gramps.gen.lib.person import Person
+from gramps.gen.lib.family import Family
+from gramps.gen.lib.src import Source
+from gramps.gen.lib.citation import Citation
+from gramps.gen.lib.event import Event
+from gramps.gen.lib.place import Place
+from gramps.gen.lib.repo import Repository
+from gramps.gen.lib.note import Note
+from gramps.gen.lib.tag import Tag
+from gramps.gen.lib.genderstats import GenderStats
+
+_LOG = logging.getLogger(DBLOGNAME)
+
+def touch(fname, mode=0o666, dir_fd=None, **kwargs):
+    ## After http://stackoverflow.com/questions/1158076/implement-touch-using-python
+    flags = os.O_CREAT | os.O_APPEND
+    with os.fdopen(os.open(fname, flags=flags, mode=mode, dir_fd=dir_fd)) as f:
+        os.utime(f.fileno() if os.utime in os.supports_fd else fname,
+                 dir_fd=None if os.supports_fd else dir_fd, **kwargs)
+
+class Environment(object):
+    """
+    Implements the Environment API.
+    """
+    def __init__(self, db):
+        self.db = db
+
+    def txn_begin(self):
+        return DBAPITxn("DBAPIDb Transaction", self.db)
+
+class Table(object):
+    """
+    Implements Table interface.
+    """
+    def __init__(self, funcs):
+        self.funcs = funcs
+
+    def cursor(self):
+        """
+        Returns a Cursor for this Table.
+        """
+        return self.funcs["cursor_func"]()
+
+    def put(self, key, data, txn=None):
+        self.funcs["add_func"](data, txn)
+
+class Map(object):
+    """
+    Implements the map API for person_map, etc.
+    
+    Takes a Table() as argument.
+    """
+    def __init__(self, table, 
+                 keys_func="handles_func", 
+                 contains_func="has_handle_func",
+                 *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.table = table
+        self.keys_func = keys_func
+        self.contains_func = contains_func
+
+    def keys(self):
+        return self.table.funcs[self.keys_func]()
+
+    def values(self):
+        return self.table.funcs["cursor_func"]()
+
+    def __contains__(self, key):
+        return self.table.funcs[self.contains_func](key)
+
+    def __getitem__(self, key):
+        if self.table.funcs[self.contains_func](key):
+            return self.table.funcs["raw_func"](key)
+
+    def __len__(self):
+        return self.table.funcs["count_func"]()
+
+class MetaCursor(object):
+    def __init__(self):
+        pass
+    def __enter__(self):
+        return self
+    def __iter__(self):
+        return self.__next__()
+    def __next__(self):
+        yield None
+    def __exit__(self, *args, **kwargs):
+        pass
+    def iter(self):
+        yield None
+    def first(self):
+        self._iter = self.__iter__()
+        return self.next()
+    def next(self):
+        try:
+            return next(self._iter)
+        except:
+            return None
+    def close(self):
+        pass
+
+class Cursor(object):
+    def __init__(self, map):
+        self.map = map
+        self._iter = self.__iter__()
+    def __enter__(self):
+        return self
+    def __iter__(self):
+        for item in self.map.keys():
+            yield (bytes(item, "utf-8"), self.map[item])
+    def __next__(self):
+        try:
+            return self._iter.__next__()
+        except StopIteration:
+            return None
+    def __exit__(self, *args, **kwargs):
+        pass
+    def iter(self):
+        for item in self.map.keys():
+            yield (bytes(item, "utf-8"), self.map[item])
+    def first(self):
+        self._iter = self.__iter__()
+        try:
+            return next(self._iter)
+        except:
+            return
+    def next(self):
+        try:
+            return next(self._iter)
+        except:
+            return
+    def close(self):
+        pass
+
+class Bookmarks(object):
+    def __init__(self):
+        self.handles = []
+    def get(self):
+        return self.handles
+    def append(self, handle):
+        self.handles.append(handle)
+
+class DBAPITxn(DbTxn):
+    def __init__(self, message, db, batch=False):
+        DbTxn.__init__(self, message, db, batch)
+
+    def get(self, key, default=None, txn=None, **kwargs):
+        """
+        Returns the data object associated with key
+        """
+        if txn and key in txn:
+            return txn[key]
+        else:
+            return None
+
+    def put(self, handle, new_data, txn):
+        """
+        """
+        txn[handle] = new_data
+
+class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
+    """
+    A Gramps Database Backend. This replicates the grampsdb functions.
+    """
+    __signals__ = dict((obj+'-'+op, signal)
+                       for obj in
+                       ['person', 'family', 'event', 'place',
+                        'source', 'citation', 'media', 'note', 'repository', 'tag']
+                       for op, signal in zip(
+                               ['add',   'update', 'delete', 'rebuild'],
+                               [(list,), (list,),  (list,),   None]
+                       )
+                   )
+    
+    # 2. Signals for long operations
+    __signals__.update(('long-op-'+op, signal) for op, signal in zip(
+        ['start',  'heartbeat', 'end'],
+        [(object,), None,       None]
+        ))
+
+    # 3. Special signal for change in home person
+    __signals__['home-person-changed'] = None
+
+    # 4. Signal for change in person group name, parameters are
+    __signals__['person-groupname-rebuild'] = (str, str)
+
+    __callback_map = {}
+
+    def __init__(self, directory=None):
+        DbReadBase.__init__(self)
+        DbWriteBase.__init__(self)
+        Callback.__init__(self)
+        self._tables['Person'].update(
+            {
+                "handle_func": self.get_person_from_handle, 
+                "gramps_id_func": self.get_person_from_gramps_id,
+                "class_func": Person,
+                "cursor_func": self.get_person_cursor,
+                "handles_func": self.get_person_handles,
+                "add_func": self.add_person,
+                "commit_func": self.commit_person,
+                "iter_func": self.iter_people,
+                "ids_func": self.get_person_gramps_ids,
+                "has_handle_func": self.has_handle_for_person,
+                "has_gramps_id_func": self.has_gramps_id_for_person,
+                "count": self.get_number_of_people,
+                "raw_func": self._get_raw_person_data,
+            })
+        self._tables['Family'].update(
+            {
+                "handle_func": self.get_family_from_handle, 
+                "gramps_id_func": self.get_family_from_gramps_id,
+                "class_func": Family,
+                "cursor_func": self.get_family_cursor,
+                "handles_func": self.get_family_handles,
+                "add_func": self.add_family,
+                "commit_func": self.commit_family,
+                "iter_func": self.iter_families,
+                "ids_func": self.get_family_gramps_ids,
+                "has_handle_func": self.has_handle_for_family,
+                "has_gramps_id_func": self.has_gramps_id_for_family,
+                "count": self.get_number_of_families,
+                "raw_func": self._get_raw_family_data,
+            })
+        self._tables['Source'].update(
+            {
+                "handle_func": self.get_source_from_handle, 
+                "gramps_id_func": self.get_source_from_gramps_id,
+                "class_func": Source,
+                "cursor_func": self.get_source_cursor,
+                "handles_func": self.get_source_handles,
+                "add_func": self.add_source,
+                "commit_func": self.commit_source,
+                "iter_func": self.iter_sources,
+                "ids_func": self.get_source_gramps_ids,
+                "has_handle_func": self.has_handle_for_source,
+                "has_gramps_id_func": self.has_gramps_id_for_source,
+                "count": self.get_number_of_sources,
+                "raw_func": self._get_raw_source_data,
+                })
+        self._tables['Citation'].update(
+            {
+                "handle_func": self.get_citation_from_handle, 
+                "gramps_id_func": self.get_citation_from_gramps_id,
+                "class_func": Citation,
+                "cursor_func": self.get_citation_cursor,
+                "handles_func": self.get_citation_handles,
+                "add_func": self.add_citation,
+                "commit_func": self.commit_citation,
+                "iter_func": self.iter_citations,
+                "ids_func": self.get_citation_gramps_ids,
+                "has_handle_func": self.has_handle_for_citation,
+                "has_gramps_id_func": self.has_gramps_id_for_citation,
+                "count": self.get_number_of_citations,
+                "raw_func": self._get_raw_citation_data,
+            })
+        self._tables['Event'].update(
+            {
+                "handle_func": self.get_event_from_handle, 
+                "gramps_id_func": self.get_event_from_gramps_id,
+                "class_func": Event,
+                "cursor_func": self.get_event_cursor,
+                "handles_func": self.get_event_handles,
+                "add_func": self.add_event,
+                "commit_func": self.commit_event,
+                "iter_func": self.iter_events,
+                "ids_func": self.get_event_gramps_ids,
+                "has_handle_func": self.has_handle_for_event,
+                "has_gramps_id_func": self.has_gramps_id_for_event,
+                "count": self.get_number_of_events,
+                "raw_func": self._get_raw_event_data,
+            })
+        self._tables['Media'].update(
+            {
+                "handle_func": self.get_object_from_handle, 
+                "gramps_id_func": self.get_object_from_gramps_id,
+                "class_func": MediaObject,
+                "cursor_func": self.get_media_cursor,
+                "handles_func": self.get_media_object_handles,
+                "add_func": self.add_object,
+                "commit_func": self.commit_media_object,
+                "iter_func": self.iter_media_objects,
+                "ids_func": self.get_media_gramps_ids,
+                "has_handle_func": self.has_handle_for_media,
+                "has_gramps_id_func": self.has_gramps_id_for_media,
+                "count": self.get_number_of_media_objects,
+                "raw_func": self._get_raw_media_data,
+            })
+        self._tables['Place'].update(
+            {
+                "handle_func": self.get_place_from_handle, 
+                "gramps_id_func": self.get_place_from_gramps_id,
+                "class_func": Place,
+                "cursor_func": self.get_place_cursor,
+                "handles_func": self.get_place_handles,
+                "add_func": self.add_place,
+                "commit_func": self.commit_place,
+                "iter_func": self.iter_places,
+                "ids_func": self.get_place_gramps_ids,
+                "has_handle_func": self.has_handle_for_place,
+                "has_gramps_id_func": self.has_gramps_id_for_place,
+                "count": self.get_number_of_places,
+                "raw_func": self._get_raw_place_data,
+            })
+        self._tables['Repository'].update(
+            {
+                "handle_func": self.get_repository_from_handle, 
+                "gramps_id_func": self.get_repository_from_gramps_id,
+                "class_func": Repository,
+                "cursor_func": self.get_repository_cursor,
+                "handles_func": self.get_repository_handles,
+                "add_func": self.add_repository,
+                "commit_func": self.commit_repository,
+                "iter_func": self.iter_repositories,
+                "ids_func": self.get_repository_gramps_ids,
+                "has_handle_func": self.has_handle_for_repository,
+                "has_gramps_id_func": self.has_gramps_id_for_repository,
+                "count": self.get_number_of_repositories,
+                "raw_func": self._get_raw_repository_data,
+            })
+        self._tables['Note'].update(
+            {
+                "handle_func": self.get_note_from_handle, 
+                "gramps_id_func": self.get_note_from_gramps_id,
+                "class_func": Note,
+                "cursor_func": self.get_note_cursor,
+                "handles_func": self.get_note_handles,
+                "add_func": self.add_note,
+                "commit_func": self.commit_note,
+                "iter_func": self.iter_notes,
+                "ids_func": self.get_note_gramps_ids,
+                "has_handle_func": self.has_handle_for_note,
+                "has_gramps_id_func": self.has_gramps_id_for_note,
+                "count": self.get_number_of_notes,
+                "raw_func": self._get_raw_note_data,
+            })
+        self._tables['Tag'].update(
+            {
+                "handle_func": self.get_tag_from_handle, 
+                "gramps_id_func": None,
+                "class_func": Tag,
+                "cursor_func": self.get_tag_cursor,
+                "handles_func": self.get_tag_handles,
+                "add_func": self.add_tag,
+                "commit_func": self.commit_tag,
+                "iter_func": self.iter_tags,
+                "count": self.get_number_of_tags,
+            })
+        # skip GEDCOM cross-ref check for now:
+        self.set_feature("skip-check-xref", True)
+        self.set_feature("skip-import-additions", True)
+        self.readonly = False
+        self.db_is_open = True
+        self.name_formats = []
+        self.bookmarks = Bookmarks()
+        self.family_bookmarks = Bookmarks()
+        self.event_bookmarks = Bookmarks()
+        self.place_bookmarks = Bookmarks()
+        self.citation_bookmarks = Bookmarks()
+        self.source_bookmarks = Bookmarks()
+        self.repo_bookmarks = Bookmarks()
+        self.media_bookmarks = Bookmarks()
+        self.note_bookmarks = Bookmarks()
+        self.set_person_id_prefix('I%04d')
+        self.set_object_id_prefix('O%04d')
+        self.set_family_id_prefix('F%04d')
+        self.set_citation_id_prefix('C%04d')
+        self.set_source_id_prefix('S%04d')
+        self.set_place_id_prefix('P%04d')
+        self.set_event_id_prefix('E%04d')
+        self.set_repository_id_prefix('R%04d')
+        self.set_note_id_prefix('N%04d')
+        # ----------------------------------
+        self.id_trans  = DBAPITxn("ID Transaction", self)
+        self.fid_trans = DBAPITxn("FID Transaction", self)
+        self.pid_trans = DBAPITxn("PID Transaction", self)
+        self.cid_trans = DBAPITxn("CID Transaction", self)
+        self.sid_trans = DBAPITxn("SID Transaction", self)
+        self.oid_trans = DBAPITxn("OID Transaction", self)
+        self.rid_trans = DBAPITxn("RID Transaction", self)
+        self.nid_trans = DBAPITxn("NID Transaction", self)
+        self.eid_trans = DBAPITxn("EID Transaction", self)
+        self.cmap_index = 0
+        self.smap_index = 0
+        self.emap_index = 0
+        self.pmap_index = 0
+        self.fmap_index = 0
+        self.lmap_index = 0
+        self.omap_index = 0
+        self.rmap_index = 0
+        self.nmap_index = 0
+        self.env = Environment(self)
+        self.person_map = Map(Table(self._tables["Person"]))
+        self.person_id_map = Map(Table(self._tables["Person"]),
+                                 keys_func="ids_func",
+                                 contains_func="has_gramps_id_func")
+        self.family_map = Map(Table(self._tables["Family"]))
+        self.family_id_map = Map(Table(self._tables["Family"]),
+                                 keys_func="ids_func",
+                                 contains_func="has_gramps_id_func")
+        self.place_map  = Map(Table(self._tables["Place"]))
+        self.place_id_map = Map(Table(self._tables["Place"]),
+                                 keys_func="ids_func",
+                                 contains_func="has_gramps_id_func")
+        self.citation_map = Map(Table(self._tables["Citation"]))
+        self.citation_id_map = Map(Table(self._tables["Citation"]),
+                                 keys_func="ids_func",
+                                 contains_func="has_gramps_id_func")
+        self.source_map = Map(Table(self._tables["Source"]))
+        self.source_id_map = Map(Table(self._tables["Source"]),
+                                 keys_func="ids_func",
+                                 contains_func="has_gramps_id_func")
+        self.repository_map  = Map(Table(self._tables["Repository"]))
+        self.repository_id_map = Map(Table(self._tables["Repository"]),
+                                 keys_func="ids_func",
+                                 contains_func="has_gramps_id_func")
+        self.note_map = Map(Table(self._tables["Note"]))
+        self.note_id_map = Map(Table(self._tables["Note"]),
+                                 keys_func="ids_func",
+                                 contains_func="has_gramps_id_func")
+        self.media_map  = Map(Table(self._tables["Media"]))
+        self.media_id_map = Map(Table(self._tables["Media"]),
+                                 keys_func="ids_func",
+                                 contains_func="has_gramps_id_func")
+        self.event_map  = Map(Table(self._tables["Event"]))
+        self.event_id_map = Map(Table(self._tables["Event"]), 
+                                 keys_func="ids_func",
+                                 contains_func="has_gramps_id_func")
+        self.tag_map  = Map(Table(self._tables["Tag"]))
+        self.metadata   = Map(Table({"cursor_func": lambda: MetaCursor()}))
+        self.name_group = {}
+        self.undo_callback = None
+        self.redo_callback = None
+        self.undo_history_callback = None
+        self.modified   = 0
+        self.txn = DBAPITxn("DBAPI Transaction", self)
+        self.transaction = None
+        self.undodb = DbUndo(self)
+        self.abort_possible = False
+        self._bm_changes = 0
+        self._directory = directory
+        self.full_name = None
+        self.path = None
+        self.brief_name = None
+        self.genderStats = GenderStats() # can pass in loaded stats as dict
+        self.owner = Researcher()
+        if directory:
+            self.load(directory)
+
+    def version_supported(self):
+        """Return True when the file has a supported version."""
+        return True
+
+    def get_table_names(self):
+        """Return a list of valid table names."""
+        return list(self._tables.keys())
+
+    def get_table_metadata(self, table_name):
+        """Return the metadata for a valid table name."""
+        if table_name in self._tables:
+            return self._tables[table_name]
+        return None
+
+    def transaction_commit(self, txn):
+        ## FIXME
+        pass
+
+    def get_undodb(self):
+        ## FIXME
+        return None
+
+    def transaction_abort(self, txn):
+        ## FIXME
+        pass
+
+    @staticmethod
+    def _validated_id_prefix(val, default):
+        if isinstance(val, str) and val:
+            try:
+                str_ = val % 1
+            except TypeError:           # missing conversion specifier
+                prefix_var = val + "%d"
+            except ValueError:          # incomplete format
+                prefix_var = default+"%04d"
+            else:
+                prefix_var = val        # OK as given
+        else:
+            prefix_var = default+"%04d" # not a string or empty string
+        return prefix_var
+
+    @staticmethod
+    def __id2user_format(id_pattern):
+        """
+        Return a method that accepts a Gramps ID and adjusts it to the users
+        format.
+        """
+        pattern_match = re.match(r"(.*)%[0 ](\d+)[diu]$", id_pattern)
+        if pattern_match:
+            str_prefix = pattern_match.group(1)
+            nr_width = int(pattern_match.group(2))
+            def closure_func(gramps_id):
+                if gramps_id and gramps_id.startswith(str_prefix):
+                    id_number = gramps_id[len(str_prefix):]
+                    if id_number.isdigit():
+                        id_value = int(id_number, 10)
+                        #if len(str(id_value)) > nr_width:
+                        #    # The ID to be imported is too large to fit in the
+                        #    # users format. For now just create a new ID,
+                        #    # because that is also what happens with IDs that
+                        #    # are identical to IDs already in the database. If
+                        #    # the problem of colliding import and already
+                        #    # present IDs is solved the code here also needs
+                        #    # some solution.
+                        #    gramps_id = id_pattern % 1
+                        #else:
+                        gramps_id = id_pattern % id_value
+                return gramps_id
+        else:
+            def closure_func(gramps_id):
+                return gramps_id
+        return closure_func
+
+    def set_person_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Person ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as I%d or I%04d.
+        """
+        self.person_prefix = self._validated_id_prefix(val, "I")
+        self.id2user_format = self.__id2user_format(self.person_prefix)
+
+    def set_citation_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Citation ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as C%d or C%04d.
+        """
+        self.citation_prefix = self._validated_id_prefix(val, "C")
+        self.cid2user_format = self.__id2user_format(self.citation_prefix)
+            
+    def set_source_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Source ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as S%d or S%04d.
+        """
+        self.source_prefix = self._validated_id_prefix(val, "S")
+        self.sid2user_format = self.__id2user_format(self.source_prefix)
+            
+    def set_object_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS MediaObject ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as O%d or O%04d.
+        """
+        self.mediaobject_prefix = self._validated_id_prefix(val, "O")
+        self.oid2user_format = self.__id2user_format(self.mediaobject_prefix)
+
+    def set_place_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Place ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as P%d or P%04d.
+        """
+        self.place_prefix = self._validated_id_prefix(val, "P")
+        self.pid2user_format = self.__id2user_format(self.place_prefix)
+
+    def set_family_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Family ID values. The string is
+        expected to be in the form of a simple text string, or in a format
+        that contains a C/Python style format string using %d, such as F%d
+        or F%04d.
+        """
+        self.family_prefix = self._validated_id_prefix(val, "F")
+        self.fid2user_format = self.__id2user_format(self.family_prefix)
+
+    def set_event_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Event ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as E%d or E%04d.
+        """
+        self.event_prefix = self._validated_id_prefix(val, "E")
+        self.eid2user_format = self.__id2user_format(self.event_prefix)
+
+    def set_repository_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Repository ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as R%d or R%04d.
+        """
+        self.repository_prefix = self._validated_id_prefix(val, "R")
+        self.rid2user_format = self.__id2user_format(self.repository_prefix)
+
+    def set_note_id_prefix(self, val):
+        """
+        Set the naming template for GRAMPS Note ID values. 
+        
+        The string is expected to be in the form of a simple text string, or 
+        in a format that contains a C/Python style format string using %d, 
+        such as N%d or N%04d.
+        """
+        self.note_prefix = self._validated_id_prefix(val, "N")
+        self.nid2user_format = self.__id2user_format(self.note_prefix)
+
+    def __find_next_gramps_id(self, prefix, map_index, trans):
+        """
+        Helper function for find_next_<object>_gramps_id methods
+        """
+        index = prefix % map_index
+        while trans.get(str(index), txn=self.txn) is not None:
+            map_index += 1
+            index = prefix % map_index
+        map_index += 1
+        return (map_index, index)
+        
+    def find_next_person_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Person object based off the 
+        person ID prefix.
+        """
+        self.pmap_index, gid = self.__find_next_gramps_id(self.person_prefix,
+                                          self.pmap_index, self.id_trans)
+        return gid
+
+    def find_next_place_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Place object based off the 
+        place ID prefix.
+        """
+        self.lmap_index, gid = self.__find_next_gramps_id(self.place_prefix,
+                                          self.lmap_index, self.pid_trans)
+        return gid
+
+    def find_next_event_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Event object based off the 
+        event ID prefix.
+        """
+        self.emap_index, gid = self.__find_next_gramps_id(self.event_prefix,
+                                          self.emap_index, self.eid_trans)
+        return gid
+
+    def find_next_object_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a MediaObject object based
+        off the media object ID prefix.
+        """
+        self.omap_index, gid = self.__find_next_gramps_id(self.mediaobject_prefix,
+                                          self.omap_index, self.oid_trans)
+        return gid
+
+    def find_next_citation_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Citation object based off the 
+        citation ID prefix.
+        """
+        self.cmap_index, gid = self.__find_next_gramps_id(self.citation_prefix,
+                                          self.cmap_index, self.cid_trans)
+        return gid
+
+    def find_next_source_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Source object based off the 
+        source ID prefix.
+        """
+        self.smap_index, gid = self.__find_next_gramps_id(self.source_prefix,
+                                          self.smap_index, self.sid_trans)
+        return gid
+
+    def find_next_family_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Family object based off the 
+        family ID prefix.
+        """
+        self.fmap_index, gid = self.__find_next_gramps_id(self.family_prefix,
+                                          self.fmap_index, self.fid_trans)
+        return gid
+
+    def find_next_repository_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Respository object based 
+        off the repository ID prefix.
+        """
+        self.rmap_index, gid = self.__find_next_gramps_id(self.repository_prefix,
+                                          self.rmap_index, self.rid_trans)
+        return gid
+
+    def find_next_note_gramps_id(self):
+        """
+        Return the next available GRAMPS' ID for a Note object based off the 
+        note ID prefix.
+        """
+        self.nmap_index, gid = self.__find_next_gramps_id(self.note_prefix,
+                                          self.nmap_index, self.nid_trans)
+        return gid
+
+    def get_mediapath(self):
+        return None
+
+    def get_name_group_keys(self):
+        return []
+
+    def get_name_group_mapping(self, key):
+        return None
+
+    def get_person_handles(self, sort_handles=False):
+        ## Fixme: implement sort
+        cur = self.dbapi.execute("select handle from person;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_family_handles(self, sort_handles=False):
+        ## Fixme: implement sort
+        cur = self.dbapi.execute("select handle from family;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_event_handles(self, sort_handles=False):
+        ## Fixme: implement sort
+        cur = self.dbapi.execute("select handle from event;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_citation_handles(self, sort_handles=False):
+        ## Fixme: implement sort
+        cur = self.dbapi.execute("select handle from citation;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_source_handles(self, sort_handles=False):
+        ## Fixme: implement sort
+        cur = self.dbapi.execute("select handle from source;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_place_handles(self, sort_handles=False):
+        ## Fixme: implement sort
+        cur = self.dbapi.execute("select handle from place;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_repository_handles(self, sort_handles=False):
+        ## Fixme: implement sort
+        cur = self.dbapi.execute("select handle from repository;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_media_object_handles(self, sort_handles=False):
+        ## Fixme: implement sort
+        cur = self.dbapi.execute("select handle from media;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_note_handles(self, sort_handles=False):
+        ## Fixme: implement sort
+        cur = self.dbapi.execute("select handle from note;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_tag_handles(self, sort_handles=False):
+        # FIXME: implement sort
+        cur = self.dbapi.execute("select handle from tag;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_event_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
+        event = None
+        if handle in self.event_map:
+            event = Event.create(self._get_raw_event_data(handle))
+        return event
+
+    def get_family_from_handle(self, handle): 
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
+        family = None
+        if handle in self.family_map:
+            family = Family.create(self._get_raw_family_data(handle))
+        return family
+
+    def get_repository_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
+        repository = None
+        if handle in self.repository_map:
+            repository = Repository.create(self._get_raw_repository_data(handle))
+        return repository
+
+    def get_person_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
+        person = None
+        if handle in self.person_map:
+            person = Person.create(self._get_raw_person_data(handle))
+        return person
+
+    def get_place_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
+        place = None
+        if handle in self.place_map:
+            place = Place.create(self._get_raw_place_data(handle))
+        return place
+
+    def get_citation_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
+        citation = None
+        if handle in self.citation_map:
+            citation = Citation.create(self._get_raw_citation_data(handle))
+        return citation
+
+    def get_source_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
+        source = None
+        if handle in self.source_map:
+            source = Source.create(self._get_raw_source_data(handle))
+        return source
+
+    def get_note_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
+        note = None
+        if handle in self.note_map:
+            note = Note.create(self._get_raw_note_data(handle))
+        return note
+
+    def get_object_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
+        media = None
+        if handle in self.media_map:
+            media = MediaObject.create(self._get_raw_media_data(handle))
+        return media
+
+    def get_tag_from_handle(self, handle):
+        if isinstance(handle, bytes):
+            handle = str(handle, "utf-8")
+        tag = None
+        if handle in self.tag_map:
+            tag = Tag.create(self._get_raw_tag_data(handle))
+        return tag
+
+    def get_default_person(self):
+        handle = self.get_default_handle()
+        if handle:
+            return self.get_person_from_handle(handle)
+        else:
+            return None
+
+    def iter_people(self):
+        return (Person.create(data[1]) for data in self.get_person_cursor())
+
+    def iter_person_handles(self):
+        return (data[0] for data in self.get_person_cursor())
+
+    def iter_families(self):
+        return (Family.create(data[1]) for data in self.get_family_cursor())
+
+    def iter_family_handles(self):
+        return (handle for handle in self.family_map.keys())
+
+    def get_tag_from_name(self, name):
+        ## Slow, but typically not too many tags:
+        for data in self.tag_map.values():
+            tag = Tag.create(data)
+            if tag.name == name:
+                return tag
+        return None
+
+    def get_person_from_gramps_id(self, gramps_id):
+        if gramps_id in self.person_id_map:
+            return Person.create(self.person_id_map[gramps_id])
+        return None
+
+    def get_family_from_gramps_id(self, gramps_id):
+        if gramps_id in self.family_id_map:
+            return Family.create(self.family_id_map[gramps_id])
+        return None
+
+    def get_citation_from_gramps_id(self, gramps_id):
+        if gramps_id in self.citation_id_map:
+            return Citation.create(self.citation_id_map[gramps_id])
+        return None
+
+    def get_source_from_gramps_id(self, gramps_id):
+        if gramps_id in self.source_id_map:
+            return Source.create(self.source_id_map[gramps_id])
+        return None
+
+    def get_event_from_gramps_id(self, gramps_id):
+        if gramps_id in self.event_id_map:
+            return Event.create(self.event_id_map[gramps_id])
+        return None
+
+    def get_media_from_gramps_id(self, gramps_id):
+        if gramps_id in self.media_id_map:
+            return MediaObject.create(self.media_id_map[gramps_id])
+        return None
+
+    def get_place_from_gramps_id(self, gramps_id):
+        if gramps_id in self.place_id_map:
+            return Place.create(self.place_id_map[gramps_id])
+        return None
+
+    def get_repository_from_gramps_id(self, gramps_id):
+        if gramps_id in self.repository_id_map:
+            return Repository.create(self.repository_id_map[gramps_id])
+        return None
+
+    def get_note_from_gramps_id(self, gramps_id):
+        if gramps_id in self.note_id_map:
+            return Note.create(self.note_id_map[gramps_id])
+        return None
+
+    def get_number_of_people(self):
+        cur = self.dbapi.execute("select count(handle) from person;")
+        row = cur.fetchone()
+        return row[0]
+
+    def get_number_of_events(self):
+        cur = self.dbapi.execute("select count(handle) from event;")
+        row = cur.fetchone()
+        return row[0]
+
+    def get_number_of_places(self):
+        cur = self.dbapi.execute("select count(handle) from place;")
+        row = cur.fetchone()
+        return row[0]
+
+    def get_number_of_tags(self):
+        cur = self.dbapi.execute("select count(handle) from tag;")
+        row = cur.fetchone()
+        return row[0]
+
+    def get_number_of_families(self):
+        cur = self.dbapi.execute("select count(handle) from family;")
+        row = cur.fetchone()
+        return row[0]
+
+    def get_number_of_notes(self):
+        cur = self.dbapi.execute("select count(handle) from note;")
+        row = cur.fetchone()
+        return row[0]
+
+    def get_number_of_citations(self):
+        cur = self.dbapi.execute("select count(handle) from citation;")
+        row = cur.fetchone()
+        return row[0]
+
+    def get_number_of_sources(self):
+        cur = self.dbapi.execute("select count(handle) from source;")
+        row = cur.fetchone()
+        return row[0]
+
+    def get_number_of_media_objects(self):
+        cur = self.dbapi.execute("select count(handle) from media;")
+        row = cur.fetchone()
+        return row[0]
+
+    def get_number_of_repositories(self):
+        cur = self.dbapi.execute("select count(handle) from repository;")
+        row = cur.fetchone()
+        return row[0]
+
+    def get_place_cursor(self):
+        return Cursor(self.place_map)
+
+    def get_person_cursor(self):
+        return Cursor(self.person_map)
+
+    def get_family_cursor(self):
+        return Cursor(self.family_map)
+
+    def get_event_cursor(self):
+        return Cursor(self.event_map)
+
+    def get_note_cursor(self):
+        return Cursor(self.note_map)
+
+    def get_tag_cursor(self):
+        return Cursor(self.tag_map)
+
+    def get_repository_cursor(self):
+        return Cursor(self.repository_map)
+
+    def get_media_cursor(self):
+        return Cursor(self.media_map)
+
+    def get_citation_cursor(self):
+        return Cursor(self.citation_map)
+
+    def get_source_cursor(self):
+        return Cursor(self.source_map)
+
+    def has_gramps_id(self, obj_key, gramps_id):
+        key2table = {
+            PERSON_KEY:     self.person_id_map, 
+            FAMILY_KEY:     self.family_id_map, 
+            SOURCE_KEY:     self.source_id_map, 
+            CITATION_KEY:   self.citation_id_map, 
+            EVENT_KEY:      self.event_id_map, 
+            MEDIA_KEY:      self.media_id_map, 
+            PLACE_KEY:      self.place_id_map, 
+            REPOSITORY_KEY: self.repository_id_map, 
+            NOTE_KEY:       self.note_id_map, 
+            }
+        return gramps_id in key2table[obj_key]
+
+    def has_person_handle(self, handle):
+        return handle in self.person_map
+
+    def has_family_handle(self, handle):
+        return handle in self.family_map
+
+    def has_citation_handle(self, handle):
+        return handle in self.citation_map
+
+    def has_source_handle(self, handle):
+        return handle in self.source_map
+
+    def has_repository_handle(self, handle):
+        return handle in self.repository_map
+
+    def has_note_handle(self, handle):
+        return handle in self.note_map
+
+    def has_place_handle(self, handle):
+        return handle in self.place_map
+
+    def has_event_handle(self, handle):
+        return handle in self.event_map
+
+    def has_tag_handle(self, handle):
+        return handle in self.tag_map
+
+    def has_object_handle(self, handle):
+        return handle in self.media_map
+
+    def has_name_group_key(self, key):
+        # FIXME:
+        return False
+
+    def set_name_group_mapping(self, key, value):
+        # FIXME:
+        pass
+
+    def set_default_person_handle(self, handle):
+        ## FIXME
+        pass
+
+    def set_mediapath(self, mediapath):
+        ## FIXME
+        pass
+
+    def get_raw_person_data(self, handle):
+        if handle in self.person_map:
+            return self.person_map[handle]
+        return None
+
+    def get_raw_family_data(self, handle):
+        if handle in self.family_map:
+            return self.family_map[handle]
+        return None
+
+    def get_raw_citation_data(self, handle):
+        if handle in self.citation_map:
+            return self.citation_map[handle]
+        return None
+
+    def get_raw_source_data(self, handle):
+        if handle in self.source_map:
+            return self.source_map[handle]
+        return None
+
+    def get_raw_repository_data(self, handle):
+        if handle in self.repository_map:
+            return self.repository_map[handle]
+        return None
+
+    def get_raw_note_data(self, handle):
+        if handle in self.note_map:
+            return self.note_map[handle]
+        return None
+
+    def get_raw_place_data(self, handle):
+        if handle in self.place_map:
+            return self.place_map[handle]
+        return None
+
+    def get_raw_object_data(self, handle):
+        if handle in self.media_map:
+            return self.media_map[handle]
+        return None
+
+    def get_raw_tag_data(self, handle):
+        if handle in self.tag_map:
+            return self.tag_map[handle]
+        return None
+
+    def get_raw_event_data(self, handle):
+        if handle in self.event_map:
+            return self.event_map[handle]
+        return None
+
+    def add_person(self, person, trans, set_gid=True):
+        if not person.handle:
+            person.handle = create_id()
+        if not person.gramps_id and set_gid:
+            person.gramps_id = self.find_next_person_gramps_id()
+        self.commit_person(person, trans)
+        return person.handle
+
+    def add_family(self, family, trans, set_gid=True):
+        if not family.handle:
+            family.handle = create_id()
+        if not family.gramps_id and set_gid:
+            family.gramps_id = self.find_next_family_gramps_id()
+        self.commit_family(family, trans)
+        return family.handle
+
+    def add_citation(self, citation, trans, set_gid=True):
+        if not citation.handle:
+            citation.handle = create_id()
+        if not citation.gramps_id and set_gid:
+            citation.gramps_id = self.find_next_citation_gramps_id()
+        self.commit_citation(citation, trans)
+        return citation.handle
+
+    def add_source(self, source, trans, set_gid=True):
+        if not source.handle:
+            source.handle = create_id()
+        if not source.gramps_id and set_gid:
+            source.gramps_id = self.find_next_source_gramps_id()
+        self.commit_source(source, trans)
+        return source.handle
+
+    def add_repository(self, repository, trans, set_gid=True):
+        if not repository.handle:
+            repository.handle = create_id()
+        if not repository.gramps_id and set_gid:
+            repository.gramps_id = self.find_next_repository_gramps_id()
+        self.commit_repository(repository, trans)
+        return repository.handle
+
+    def add_note(self, note, trans, set_gid=True):
+        if not note.handle:
+            note.handle = create_id()
+        if not note.gramps_id and set_gid:
+            note.gramps_id = self.find_next_note_gramps_id()
+        self.commit_note(note, trans)
+        return note.handle
+
+    def add_place(self, place, trans, set_gid=True):
+        if not place.handle:
+            place.handle = create_id()
+        if not place.gramps_id and set_gid:
+            place.gramps_id = self.find_next_place_gramps_id()
+        self.commit_place(place, trans)
+        return place.handle
+
+    def add_event(self, event, trans, set_gid=True):
+        if not event.handle:
+            event.handle = create_id()
+        if not event.gramps_id and set_gid:
+            event.gramps_id = self.find_next_event_gramps_id()
+        self.commit_event(event, trans)
+        return event.handle
+
+    def add_tag(self, tag, trans):
+        if not tag.handle:
+            tag.handle = create_id()
+        self.commit_tag(tag, trans)
+        return tag.handle
+
+    def add_object(self, obj, transaction, set_gid=True):
+        """
+        Add a MediaObject to the database, assigning internal IDs if they have
+        not already been defined.
+        
+        If not set_gid, then gramps_id is not set.
+        """
+        if not obj.handle:
+            obj.handle = create_id()
+        if not obj.gramps_id and set_gid:
+            obj.gramps_id = self.find_next_object_gramps_id()
+        self.commit_media_object(obj, transaction)
+        return obj.handle
+
+    def commit_person(self, person, trans, change_time=None):
+        emit = None
+        if True or not trans.batch:
+            if person.handle in self.person_map:
+                emit = "person-update"
+                self.dbapi.execute("""UPDATE person SET gramps_id = ?, 
+                                                        blob = ? 
+                                                    WHERE handle = ?;""",
+                                   [person.gramps_id, 
+                                    pickle.dumps(person.serialize()),
+                                    person.handle])
+            else:
+                emit = "person-add"
+                self.dbapi.execute("""insert into person(handle, gramps_id, blob) 
+                                                  values(?, ?, ?);""", 
+                                   [person.handle, person.gramps_id, pickle.dumps(person.serialize())])
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([person.handle],))
+
+    def commit_family(self, family, trans, change_time=None):
+        emit = None
+        if True or not trans.batch:
+            if family.handle in self.family_map:
+                emit = "family-update"
+                self.dbapi.execute("""UPDATE family SET gramps_id = ?, 
+                                                        blob = ? 
+                                                    WHERE handle = ?;""",
+                                   [family.gramps_id, 
+                                    pickle.dumps(family.serialize()),
+                                    family.handle])
+            else:
+                emit = "family-add"
+                self.dbapi.execute("insert into family values(?, ?, ?);", 
+                                   [family.handle, family.gramps_id, 
+                                    pickle.dumps(family.serialize())])
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([family.handle],))
+
+    def commit_citation(self, citation, trans, change_time=None):
+        emit = None
+        if True or not trans.batch:
+            if citation.handle in self.citation_map:
+                emit = "citation-update"
+                self.dbapi.execute("""UPDATE citation SET gramps_id = ?, 
+                                                        blob = ? 
+                                                    WHERE handle = ?;""",
+                                   [citation.gramps_id, 
+                                    pickle.dumps(citation.serialize()),
+                                    citation.handle])
+            else:
+                emit = "citation-add"
+                self.dbapi.execute("insert into citation values(?, ?, ?);", 
+                           [citation.handle, citation.gramps_id, pickle.dumps(citation.serialize())])
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([citation.handle],))
+
+    def commit_source(self, source, trans, change_time=None):
+        emit = None
+        if True or not trans.batch:
+            if source.handle in self.source_map:
+                emit = "source-update"
+                self.dbapi.execute("""UPDATE source SET gramps_id = ?, 
+                                                        blob = ? 
+                                                    WHERE handle = ?;""",
+                                   [source.gramps_id, 
+                                    pickle.dumps(source.serialize()),
+                                    source.handle])
+            else:
+                emit = "source-add"
+                self.dbapi.execute("insert into source values(?, ?, ?);", 
+                           [source.handle, source.gramps_id, pickle.dumps(source.serialize())])
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([source.handle],))
+
+    def commit_repository(self, repository, trans, change_time=None):
+        emit = None
+        if True or not trans.batch:
+            if repository.handle in self.repository_map:
+                emit = "repository-update"
+                self.dbapi.execute("""UPDATE repository SET gramps_id = ?, 
+                                                        blob = ? 
+                                                    WHERE handle = ?;""",
+                                   [repository.gramps_id, 
+                                    pickle.dumps(repository.serialize()),
+                                    repository.handle])
+            else:
+                emit = "repository-add"
+                self.dbapi.execute("insert into repository values(?, ?, ?);", 
+                           [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())])
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([repository.handle],))
+
+    def commit_note(self, note, trans, change_time=None):
+        emit = None
+        if True or not trans.batch:
+            if note.handle in self.note_map:
+                emit = "note-update"
+                self.dbapi.execute("""UPDATE note SET gramps_id = ?, 
+                                                        blob = ? 
+                                                    WHERE handle = ?;""",
+                                   [note.gramps_id, 
+                                    pickle.dumps(note.serialize()),
+                                    note.handle])
+            else:
+                emit = "note-add"
+                self.dbapi.execute("insert into note values(?, ?, ?);", 
+                           [note.handle, note.gramps_id, pickle.dumps(note.serialize())])
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([note.handle],))
+
+    def commit_place(self, place, trans, change_time=None):
+        emit = None
+        if True or not trans.batch:
+            if place.handle in self.place_map:
+                emit = "place-update"
+                self.dbapi.execute("""UPDATE place SET gramps_id = ?, 
+                                                        blob = ? 
+                                                    WHERE handle = ?;""",
+                                   [place.gramps_id, 
+                                    pickle.dumps(place.serialize()),
+                                    place.handle])
+            else:
+                emit = "place-add"
+                self.dbapi.execute("insert into place values(?, ?, ?);", 
+                           [place.handle, place.gramps_id, pickle.dumps(place.serialize())])
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([place.handle],))
+
+    def commit_event(self, event, trans, change_time=None):
+        emit = None
+        if True or not trans.batch:
+            if event.handle in self.event_map:
+                emit = "event-update"
+                self.dbapi.execute("""UPDATE event SET gramps_id = ?, 
+                                                        blob = ? 
+                                                    WHERE handle = ?;""",
+                                   [event.gramps_id, 
+                                    pickle.dumps(event.serialize()),
+                                    event.handle])
+            else:
+                emit = "event-add"
+                self.dbapi.execute("insert into event values(?, ?, ?);", 
+                           [event.handle, event.gramps_id, pickle.dumps(event.serialize())])
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([event.handle],))
+
+    def commit_tag(self, tag, trans, change_time=None):
+        emit = None
+        if True or not trans.batch:
+            if tag.handle in self.tag_map:
+                emit = "tag-update"
+                self.dbapi.execute("""UPDATE tag SET blob = ? 
+                                             WHERE handle = ?;""",
+                                   [pickle.dumps(tag.serialize()),
+                                    tag.handle])
+            else:
+                emit = "tag-add"
+                self.dbapi.execute("insert into tag values(?, ?);", 
+                           [tag.handle, pickle.dumps(tag.serialize())])
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([tag.handle],))
+
+    def commit_media_object(self, media, trans, change_time=None):
+        emit = None
+        if True or not trans.batch:
+            if media.handle in self.media_map:
+                emit = "media-update"
+                self.dbapi.execute("""UPDATE media SET gramps_id = ?, 
+                                                        blob = ? 
+                                                    WHERE handle = ?;""",
+                                   [media.gramps_id, 
+                                    pickle.dumps(media.serialize()),
+                                    media.handle])
+            else:
+                emit = "media-add"
+                self.dbapi.execute("insert into media values(?, ?, ?);", 
+                           [media.handle, media.gramps_id, pickle.dumps(media.serialize())])
+        # Emit after added:
+        if emit:
+            self.emit(emit, ([media.handle],))
+
+    def get_gramps_ids(self, obj_key):
+        key2table = {
+            PERSON_KEY:     self.person_id_map, 
+            FAMILY_KEY:     self.family_id_map, 
+            CITATION_KEY:   self.citation_id_map, 
+            SOURCE_KEY:     self.source_id_map, 
+            EVENT_KEY:      self.event_id_map, 
+            MEDIA_KEY:      self.media_id_map, 
+            PLACE_KEY:      self.place_id_map, 
+            REPOSITORY_KEY: self.repository_id_map, 
+            NOTE_KEY:       self.note_id_map, 
+            }
+        return list(key2table[obj_key].keys())
+
+    def transaction_begin(self, transaction):
+        ## FIXME
+        return 
+
+    def set_researcher(self, owner):
+        self.owner.set_from(owner)
+
+    def get_researcher(self):
+        return self.owner
+
+    def request_rebuild(self):
+        self.emit('person-rebuild')
+        self.emit('family-rebuild')
+        self.emit('place-rebuild')
+        self.emit('source-rebuild')
+        self.emit('citation-rebuild')
+        self.emit('media-rebuild')
+        self.emit('event-rebuild')
+        self.emit('repository-rebuild')
+        self.emit('note-rebuild')
+        self.emit('tag-rebuild')
+
+    def copy_from_db(self, db):
+        """
+        A (possibily) implementation-specific method to get data from
+        db into this database.
+        """
+        for key in db._tables.keys():
+            cursor = db._tables[key]["cursor_func"]
+            class_ = db._tables[key]["class_func"]
+            for (handle, data) in cursor():
+                map = getattr(self, "%s_map" % key.lower())
+                map[handle] = class_.create(data)
+
+    def get_transaction_class(self):
+        """
+        Get the transaction class associated with this database backend.
+        """
+        return DBAPITxn
+
+    def get_from_name_and_handle(self, table_name, handle):
+        """
+        Returns a gen.lib object (or None) given table_name and
+        handle.
+
+        Examples:
+
+        >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008")
+        >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23")
+        """
+        if table_name in self._tables:
+            return self._tables[table_name]["handle_func"](handle)
+        return None
+
+    def get_from_name_and_gramps_id(self, table_name, gramps_id):
+        """
+        Returns a gen.lib object (or None) given table_name and
+        Gramps ID.
+
+        Examples:
+
+        >>> self.get_from_name_and_gramps_id("Person", "I00002")
+        >>> self.get_from_name_and_gramps_id("Family", "F056")
+        >>> self.get_from_name_and_gramps_id("Media", "M00012")
+        """
+        if table_name in self._tables:
+            return self._tables[table_name]["gramps_id_func"](gramps_id)
+        return None
+
+    def remove_person(self, handle, transaction):
+        """
+        Remove the Person specified by the database handle from the database, 
+        preserving the change in the passed transaction. 
+        """
+
+        if self.readonly or not handle:
+            return
+        if handle in self.person_map:
+            person = Person.create(self.person_map[handle])
+            #del self.person_map[handle]
+            #del self.person_id_map[person.gramps_id]
+            self.dbapi.execute("DELETE from person WHERE handle = ?;", [handle])
+            self.emit("person-delete", ([handle],))
+
+    def remove_source(self, handle, transaction):
+        """
+        Remove the Source specified by the database handle from the
+        database, preserving the change in the passed transaction. 
+        """
+        self.__do_remove(handle, transaction, self.source_map, 
+                         self.source_id_map, SOURCE_KEY)
+
+    def remove_citation(self, handle, transaction):
+        """
+        Remove the Citation specified by the database handle from the
+        database, preserving the change in the passed transaction. 
+        """
+        self.__do_remove(handle, transaction, self.citation_map, 
+                         self.citation_id_map, CITATION_KEY)
+
+    def remove_event(self, handle, transaction):
+        """
+        Remove the Event specified by the database handle from the
+        database, preserving the change in the passed transaction. 
+        """
+        self.__do_remove(handle, transaction, self.event_map, 
+                         self.event_id_map, EVENT_KEY)
+
+    def remove_object(self, handle, transaction):
+        """
+        Remove the MediaObjectPerson specified by the database handle from the
+        database, preserving the change in the passed transaction. 
+        """
+        self.__do_remove(handle, transaction, self.media_map, 
+                         self.media_id_map, MEDIA_KEY)
+
+    def remove_place(self, handle, transaction):
+        """
+        Remove the Place specified by the database handle from the
+        database, preserving the change in the passed transaction. 
+        """
+        self.__do_remove(handle, transaction, self.place_map, 
+                         self.place_id_map, PLACE_KEY)
+
+    def remove_family(self, handle, transaction):
+        """
+        Remove the Family specified by the database handle from the
+        database, preserving the change in the passed transaction. 
+        """
+        self.__do_remove(handle, transaction, self.family_map, 
+                         self.family_id_map, FAMILY_KEY)
+
+    def remove_repository(self, handle, transaction):
+        """
+        Remove the Repository specified by the database handle from the
+        database, preserving the change in the passed transaction. 
+        """
+        self.__do_remove(handle, transaction, self.repository_map, 
+                         self.repository_id_map, REPOSITORY_KEY)
+
+    def remove_note(self, handle, transaction):
+        """
+        Remove the Note specified by the database handle from the
+        database, preserving the change in the passed transaction. 
+        """
+        self.__do_remove(handle, transaction, self.note_map, 
+                         self.note_id_map, NOTE_KEY)
+
+    def remove_tag(self, handle, transaction):
+        """
+        Remove the Tag specified by the database handle from the
+        database, preserving the change in the passed transaction. 
+        """
+        self.__do_remove(handle, transaction, self.tag_map, 
+                         None, TAG_KEY)
+
+    def is_empty(self):
+        """
+        Return true if there are no [primary] records in the database
+        """
+        for table in self._tables:
+            if len(self._tables[table]["handles_func"]()) > 0:
+                return False
+        return True
+
+    def __do_remove(self, handle, transaction, data_map, data_id_map, key):
+        key2table = {
+            PERSON_KEY:     "person", 
+            FAMILY_KEY:     "family", 
+            SOURCE_KEY:     "source", 
+            CITATION_KEY:   "citation", 
+            EVENT_KEY:      "event", 
+            MEDIA_KEY:      "media", 
+            PLACE_KEY:      "place", 
+            REPOSITORY_KEY: "repository", 
+            NOTE_KEY:       "note", 
+            }
+        if self.readonly or not handle:
+            return
+        if handle in data_map:
+            self.dbapi.execute("DELETE from %s WHERE handle = ?;" % key2table[key], 
+                               [handle])
+            self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],))
+
+    def delete_primary_from_reference_map(self, handle, transaction, txn=None):
+        """
+        Remove all references to the primary object from the reference_map.
+        handle should be utf-8
+        """
+        primary_cur = self.get_reference_map_primary_cursor()
+
+        try:
+            ret = primary_cur.set(handle)
+        except:
+            ret = None
+        
+        remove_list = set()
+        while (ret is not None):
+            (key, data) = ret
+            
+            # data values are of the form:
+            #   ((primary_object_class_name, primary_object_handle),
+            #    (referenced_object_class_name, referenced_object_handle))
+            
+            # so we need the second tuple give us a reference that we can
+            # combine with the primary_handle to get the main key.
+            main_key = (handle.decode('utf-8'), pickle.loads(data)[1][1])
+            
+            # The trick is not to remove while inside the cursor,
+            # but collect them all and remove after the cursor is closed
+            remove_list.add(main_key)
+
+            ret = primary_cur.next_dup()
+
+        primary_cur.close()
+
+        # Now that the cursor is closed, we can remove things
+        for main_key in remove_list:
+            self.__remove_reference(main_key, transaction, txn)
+
+    def __remove_reference(self, key, transaction, txn):
+        """
+        Remove the reference specified by the key, preserving the change in 
+        the passed transaction.
+        """
+        if isinstance(key, tuple):
+            #create a byte string key, first validity check in python 3!
+            for val in key:
+                if isinstance(val, bytes):
+                    raise DbError(_('An attempt is made to save a reference key '
+                        'which is partly bytecode, this is not allowed.\n'
+                        'Key is %s') % str(key))
+            key = str(key)
+        if isinstance(key, str):
+            key = key.encode('utf-8')
+        if not self.readonly:
+            if not transaction.batch:
+                old_data = self.reference_map.get(key, txn=txn)
+                transaction.add(REFERENCE_KEY, TXNDEL, key, old_data, None)
+                #transaction.reference_del.append(str(key))
+            self.reference_map.delete(key, txn=txn)
+
+    ## Missing:
+
+    def backup(self):
+        ## FIXME
+        pass
+
+    def close(self):
+        if self._directory:
+            from gramps.plugins.export.exportxml import XmlWriter
+            from gramps.cli.user import User 
+            writer = XmlWriter(self, User(), strip_photos=0, compress=1)
+            filename = os.path.join(self._directory, "data.gramps")
+            writer.write(filename)
+            filename = os.path.join(self._directory, "meta_data.db")
+            touch(filename)
+
+    def find_backlink_handles(self, handle, include_classes=None):
+        ## FIXME
+        return []
+
+    def find_initial_person(self):
+        items = self.person_map.keys()
+        if len(items) > 0:
+            return self.get_person_from_handle(list(items)[0])
+        return None
+
+    def find_place_child_handles(self, handle):
+        ## FIXME
+        return []
+
+    def get_bookmarks(self):
+        return self.bookmarks
+
+    def get_child_reference_types(self):
+        ## FIXME
+        return []
+
+    def get_citation_bookmarks(self):
+        return self.citation_bookmarks
+
+    def get_cursor(self, table, txn=None, update=False, commit=False):
+        ## FIXME
+        ## called from a complete find_back_ref
+        pass
+
+    # cursors for lookups in the reference_map for back reference
+    # lookups. The reference_map has three indexes:
+    # the main index: a tuple of (primary_handle, referenced_handle)
+    # the primary_handle index: the primary_handle
+    # the referenced_handle index: the referenced_handle
+    # the main index is unique, the others allow duplicate entries.
+
+    def get_default_handle(self):
+        items = self.person_map.keys()
+        if len(items) > 0:
+            return list(items)[0]
+        return None
+
+    def get_event_attribute_types(self):
+        ## FIXME
+        return []
+
+    def get_event_bookmarks(self):
+        return self.event_bookmarks
+
+    def get_event_roles(self):
+        ## FIXME
+        return []
+
+    def get_event_types(self):
+        ## FIXME
+        return []
+
+    def get_family_attribute_types(self):
+        ## FIXME
+        return []
+
+    def get_family_bookmarks(self):
+        return self.family_bookmarks
+
+    def get_family_event_types(self):
+        ## FIXME
+        return []
+
+    def get_family_relation_types(self):
+        ## FIXME
+        return []
+
+    def get_media_attribute_types(self):
+        ## FIXME
+        return []
+
+    def get_media_bookmarks(self):
+        return self.media_bookmarks
+
+    def get_name_types(self):
+        ## FIXME
+        return []
+
+    def get_note_bookmarks(self):
+        return self.note_bookmarks
+
+    def get_note_types(self):
+        ## FIXME
+        return []
+
+    def get_origin_types(self):
+        ## FIXME
+        return []
+
+    def get_person_attribute_types(self):
+        ## FIXME
+        return []
+
+    def get_person_event_types(self):
+        ## FIXME
+        return []
+
+    def get_place_bookmarks(self):
+        return self.place_bookmarks
+
+    def get_place_tree_cursor(self):
+        ## FIXME
+        return []
+
+    def get_place_types(self):
+        ## FIXME
+        return []
+
+    def get_repo_bookmarks(self):
+        return self.repo_bookmarks
+
+    def get_repository_types(self):
+        ## FIXME
+        return []
+
+    def get_save_path(self):
+        return self._directory
+
+    def get_source_attribute_types(self):
+        ## FIXME
+        return []
+
+    def get_source_bookmarks(self):
+        return self.source_bookmarks
+
+    def get_source_media_types(self):
+        ## FIXME
+        return []
+
+    def get_surname_list(self):
+        ## FIXME
+        return []
+
+    def get_url_types(self):
+        ## FIXME
+        return []
+
+    def has_changed(self):
+        ## FIXME
+        return True
+
+    def is_open(self):
+        return self._directory is not None
+
+    def iter_citation_handles(self):
+        return (data[0] for data in self.get_citation_cursor())
+
+    def iter_citations(self):
+        return (Citation.create(data[1]) for data in self.get_citation_cursor())
+
+    def iter_event_handles(self):
+        return (data[0] for data in self.get_event_cursor())
+
+    def iter_events(self):
+        return (Event.create(data[1]) for data in self.get_event_cursor())
+
+    def iter_media_objects(self):
+        return (MediaObject.create(data[1]) for data in self.get_media_cursor())
+
+    def iter_note_handles(self):
+        return (data[0] for data in self.get_note_cursor())
+
+    def iter_notes(self):
+        return (Note.create(data[1]) for data in self.get_note_cursor())
+
+    def iter_place_handles(self):
+        return (data[0] for data in self.get_place_cursor())
+
+    def iter_places(self):
+        return (Place.create(data[1]) for data in self.get_place_cursor())
+
+    def iter_repositories(self):
+        return (Repository.create(data[1]) for data in self.get_repository_cursor())
+
+    def iter_repository_handles(self):
+        return (data[0] for data in self.get_repository_cursor())
+
+    def iter_source_handles(self):
+        return (data[0] for data in self.get_source_cursor())
+
+    def iter_sources(self):
+        return (Source.create(data[1]) for data in self.get_source_cursor())
+
+    def iter_tag_handles(self):
+        return (data[0] for data in self.get_tag_cursor())
+
+    def iter_tags(self):
+        return (Tag.create(data[1]) for data in self.get_tag_cursor())
+
+    def load(self, directory, pulse_progress=None, mode=None, 
+             force_schema_upgrade=False, 
+             force_bsddb_upgrade=False, 
+             force_bsddb_downgrade=False, 
+             force_python_upgrade=False):
+        # Run code from directory
+        import sqlite3
+        self.dbapi = sqlite3.connect(':memory:')
+        self.dbapi.row_factory = sqlite3.Row # allows access by name
+        # make sure schema is up to date:
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS person (
+                                    handle    TEXT PRIMARY KEY NOT NULL,
+                                    gramps_id TEXT             ,
+                                    blob      TEXT
+        );""")
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS family (
+                                    handle    TEXT PRIMARY KEY NOT NULL,
+                                    gramps_id TEXT             ,
+                                    blob      TEXT
+        );""")
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS source (
+                                    handle    TEXT PRIMARY KEY NOT NULL,
+                                    gramps_id TEXT             ,
+                                    blob      TEXT
+        );""")
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS citation (
+                                    handle    TEXT PRIMARY KEY NOT NULL,
+                                    gramps_id TEXT             ,
+                                    blob      TEXT
+        );""")
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS event (
+                                    handle    TEXT PRIMARY KEY NOT NULL,
+                                    gramps_id TEXT             ,
+                                    blob      TEXT
+        );""")
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS media (
+                                    handle    TEXT PRIMARY KEY NOT NULL,
+                                    gramps_id TEXT             ,
+                                    blob      TEXT
+        );""")
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS place (
+                                    handle    TEXT PRIMARY KEY NOT NULL,
+                                    gramps_id TEXT             ,
+                                    blob      TEXT
+        );""")
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS repository (
+                                    handle    TEXT PRIMARY KEY NOT NULL,
+                                    gramps_id TEXT             ,
+                                    blob      TEXT
+        );""")
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS note (
+                                    handle    TEXT PRIMARY KEY NOT NULL,
+                                    gramps_id TEXT             ,
+                                    blob      TEXT
+        );""")
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS tag (
+                                    handle    TEXT PRIMARY KEY NOT NULL,
+                                    blob      TEXT
+        );""")
+
+    def redo(self, update_history=True):
+        ## FIXME
+        pass
+
+    def restore(self):
+        ## FIXME
+        pass
+
+    def set_prefixes(self, person, media, family, source, citation, 
+                     place, event, repository, note):
+        ## FIXME
+        pass
+
+    def set_save_path(self, directory):
+        self._directory = directory
+        self.full_name = os.path.abspath(self._directory)
+        self.path = self.full_name
+        self.brief_name = os.path.basename(self._directory)
+
+    def undo(self, update_history=True):
+        ## FIXME
+        pass
+
+    def write_version(self, directory):
+        """Write files for a newly created DB."""
+        versionpath = os.path.join(directory, str(DBBACKEND))
+        _LOG.debug("Write database backend file to 'dbapi'")
+        with open(versionpath, "w") as version_file:
+            version_file.write("dbapi")
+
+    def report_bm_change(self):
+        """
+        Add 1 to the number of bookmark changes during this session.
+        """
+        self._bm_changes += 1
+
+    def db_has_bm_changes(self):
+        """
+        Return whethere there were bookmark changes during the session.
+        """
+        return self._bm_changes > 0
+
+    def get_summary(self):
+        """
+        Returns dictionary of summary item.
+        Should include, if possible:
+
+        _("Number of people")
+        _("Version")
+        _("Schema version")
+        """
+        return {
+            _("Number of people"): self.get_number_of_people(),
+        }
+
+    def get_dbname(self):
+        """
+        In DBAPI, the database is in a text file at the path
+        """
+        filepath = os.path.join(self._directory, "name.txt")
+        try:
+            name_file = open(filepath, "r")
+            name = name_file.readline().strip()
+            name_file.close()
+        except (OSError, IOError) as msg:
+            _LOG.error(str(msg))
+            name = None
+        return name
+
+    def reindex_reference_map(self):
+        ## FIXME
+        pass
+
+    def rebuild_secondary(self, update):
+        ## FIXME
+        pass
+
+    def prepare_import(self):
+        """
+        DBAPI does not commit data on gedcom import, but saves them
+        for later commit.
+        """
+        pass
+
+    def commit_import(self):
+        """
+        Commits the items that were queued up during the last gedcom
+        import for two step adding.
+        """
+        pass
+
+    def has_handle_for_person(self, key):
+        cur = self.dbapi.execute("select * from person where handle = ?", [key])
+        return cur.fetchone() != None
+
+    def has_handle_for_family(self, key):
+        cur = self.dbapi.execute("select * from family where handle = ?", [key])
+        return cur.fetchone() != None
+
+    def has_handle_for_source(self, key):
+        cur = self.dbapi.execute("select * from source where handle = ?", [key])
+        return cur.fetchone() != None
+
+    def has_handle_for_citation(self, key):
+        cur = self.dbapi.execute("select * from citation where handle = ?", [key])
+        return cur.fetchone() != None
+
+    def has_handle_for_event(self, key):
+        cur = self.dbapi.execute("select * from event where handle = ?", [key])
+        return cur.fetchone() != None
+
+    def has_handle_for_media(self, key):
+        cur = self.dbapi.execute("select * from media where handle = ?", [key])
+        return cur.fetchone() != None
+
+    def has_handle_for_place(self, key):
+        cur = self.dbapi.execute("select * from place where handle = ?", [key])
+        return cur.fetchone() != None
+
+    def has_handle_for_repository(self, key):
+        cur = self.dbapi.execute("select * from repository where handle = ?", [key])
+        return cur.fetchone() != None
+
+    def has_handle_for_note(self, key):
+        cur = self.dbapi.execute("select * from note where handle = ?", [key])
+        return cur.fetchone() != None
+
+    def has_gramps_id_for_person(self, key):
+        cur = self.dbapi.execute("select * from person where gramps_id = ?", [key])
+        return cur.fetchone() != None
+
+    def has_gramps_id_for_family(self, key):
+        cur = self.dbapi.execute("select * from family where gramps_id = ?", [key])
+        return cur.fetchone() != None
+
+    def has_gramps_id_for_source(self, key):
+        cur = self.dbapi.execute("select * from source where gramps_id = ?", [key])
+        return cur.fetchone() != None
+
+    def has_gramps_id_for_citation(self, key):
+        cur = self.dbapi.execute("select * from citation where gramps_id = ?", [key])
+        return cur.fetchone() != None
+
+    def has_gramps_id_for_event(self, key):
+        cur = self.dbapi.execute("select * from event where gramps_id = ?", [key])
+        return cur.fetchone() != None
+
+    def has_gramps_id_for_media(self, key):
+        cur = self.dbapi.execute("select * from media where gramps_id = ?", [key])
+        return cur.fetchone() != None
+
+    def has_gramps_id_for_place(self, key):
+        cur = self.dbapi.execute("select * from place where gramps_id = ?", [key])
+        return cur.fetchone() != None
+
+    def has_gramps_id_for_repository(self, key):
+        cur = self.dbapi.execute("select * from repository where gramps_id = ?", [key])
+        return cur.fetchone() != None
+
+    def has_gramps_id_for_note(self, key):
+        cur = self.dbapi.execute("select * from note where gramps_id = ?", [key])
+        return cur.fetchone() != None
+
+    def get_person_gramps_ids(self):
+        cur = self.dbapi.execute("select gramps_id from person;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_family_gramps_ids(self):
+        cur = self.dbapi.execute("select gramps_id from family;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_source_gramps_ids(self):
+        cur = self.dbapi.execute("select gramps_id from source;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_citation_gramps_ids(self):
+        cur = self.dbapi.execute("select gramps_id from citation;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_event_gramps_ids(self):
+        cur = self.dbapi.execute("select gramps_id from event;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_media_gramps_ids(self):
+        cur = self.dbapi.execute("select gramps_id from media;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_place_gramps_ids(self):
+        cur = self.dbapi.execute("select gramps from place;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_repository_gramps_ids(self):
+        cur = self.dbapi.execute("select gramps_id from repository;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def get_note_gramps_ids(self):
+        cur = self.dbapi.execute("select gramps_id from note;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
+
+    def _get_raw_person_data(self, key):
+        cur = self.dbapi.execute("select blob from person where handle = ?", [key])
+        row = cur.fetchone()
+        if row:
+            return pickle.loads(row[0])
+
+    def _get_raw_family_data(self, key):
+        cur = self.dbapi.execute("select blob from family where handle = ?", [key])
+        row = cur.fetchone()
+        if row:
+            return pickle.loads(row[0])
+
+    def _get_raw_source_data(self, key):
+        cur = self.dbapi.execute("select blob from source where handle = ?", [key])
+        row = cur.fetchone()
+        if row:
+            return pickle.loads(row[0])
+
+    def _get_raw_citation_data(self, key):
+        cur = self.dbapi.execute("select blob from citation where handle = ?", [key])
+        row = cur.fetchone()
+        if row:
+            return pickle.loads(row[0])
+
+    def _get_raw_event_data(self, key):
+        cur = self.dbapi.execute("select blob from event where handle = ?", [key])
+        row = cur.fetchone()
+        if row:
+            return pickle.loads(row[0])
+
+    def _get_raw_media_data(self, key):
+        cur = self.dbapi.execute("select blob from media where handle = ?", [key])
+        row = cur.fetchone()
+        if row:
+            return pickle.loads(row[0])
+
+    def _get_raw_place_data(self, key):
+        cur = self.dbapi.execute("select blob from place where handle = ?", [key])
+        row = cur.fetchone()
+        if row:
+            return pickle.loads(row[0])
+
+    def _get_raw_repository_data(self, key):
+        cur = self.dbapi.execute("select blob from repository where handle = ?", [key])
+        row = cur.fetchone()
+        if row:
+            return pickle.loads(row[0])
+
+    def _get_raw_note_data(self, key):
+        cur = self.dbapi.execute("select blob from note where handle = ?", [key])
+        row = cur.fetchone()
+        if row:
+            return pickle.loads(row[0])
+
+    def _get_raw_tag_data(self, key):
+        cur = self.dbapi.execute("select blob from tag where handle = ?", [key])
+        row = cur.fetchone()
+        if row:
+            return pickle.loads(row[0])
+
diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 2eda2460d..8b8d8beb7 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -1690,7 +1690,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return (key for key in self.event_map.keys())
 
     def iter_events(self):
-        return (Events.create(key) for key in self.event_map.values())
+        return (Event.create(key) for key in self.event_map.values())
 
     def iter_media_objects(self):
         return (MediaObject.create(key) for key in self.media_map.values())

From 31fe15e7826daa82ec7badfd015e5710e7ff4878 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 23 May 2015 02:15:44 -0400
Subject: [PATCH 087/105] DB-API 2.0 can now load/save from file; need to
 load/save metadata

---
 gramps/plugins/database/dbapi.py              | 31 +++++++++++++++++--
 .../defaults/default_settings.py              |  7 +++++
 2 files changed, 35 insertions(+), 3 deletions(-)
 create mode 100644 gramps/plugins/database/dbapi_support/defaults/default_settings.py

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index b5d61e5dc..c99146b6d 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -9,6 +9,7 @@ import time
 import re
 import os
 import logging
+import shutil
 
 #------------------------------------------------------------------------
 #
@@ -1261,6 +1262,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 self.dbapi.execute("""insert into person(handle, gramps_id, blob) 
                                                   values(?, ?, ?);""", 
                                    [person.handle, person.gramps_id, pickle.dumps(person.serialize())])
+            self.dbapi.commit()
         # Emit after added:
         if emit:
             self.emit(emit, ([person.handle],))
@@ -1281,6 +1283,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 self.dbapi.execute("insert into family values(?, ?, ?);", 
                                    [family.handle, family.gramps_id, 
                                     pickle.dumps(family.serialize())])
+            self.dbapi.commit()
         # Emit after added:
         if emit:
             self.emit(emit, ([family.handle],))
@@ -1300,6 +1303,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "citation-add"
                 self.dbapi.execute("insert into citation values(?, ?, ?);", 
                            [citation.handle, citation.gramps_id, pickle.dumps(citation.serialize())])
+            self.dbapi.commit()
         # Emit after added:
         if emit:
             self.emit(emit, ([citation.handle],))
@@ -1319,6 +1323,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "source-add"
                 self.dbapi.execute("insert into source values(?, ?, ?);", 
                            [source.handle, source.gramps_id, pickle.dumps(source.serialize())])
+            self.dbapi.commit()
         # Emit after added:
         if emit:
             self.emit(emit, ([source.handle],))
@@ -1338,6 +1343,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "repository-add"
                 self.dbapi.execute("insert into repository values(?, ?, ?);", 
                            [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())])
+            self.dbapi.commit()
         # Emit after added:
         if emit:
             self.emit(emit, ([repository.handle],))
@@ -1357,6 +1363,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "note-add"
                 self.dbapi.execute("insert into note values(?, ?, ?);", 
                            [note.handle, note.gramps_id, pickle.dumps(note.serialize())])
+            self.dbapi.commit()
         # Emit after added:
         if emit:
             self.emit(emit, ([note.handle],))
@@ -1376,6 +1383,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "place-add"
                 self.dbapi.execute("insert into place values(?, ?, ?);", 
                            [place.handle, place.gramps_id, pickle.dumps(place.serialize())])
+            self.dbapi.commit()
         # Emit after added:
         if emit:
             self.emit(emit, ([place.handle],))
@@ -1395,6 +1403,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "event-add"
                 self.dbapi.execute("insert into event values(?, ?, ?);", 
                            [event.handle, event.gramps_id, pickle.dumps(event.serialize())])
+            self.dbapi.commit()
         # Emit after added:
         if emit:
             self.emit(emit, ([event.handle],))
@@ -1412,6 +1421,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "tag-add"
                 self.dbapi.execute("insert into tag values(?, ?);", 
                            [tag.handle, pickle.dumps(tag.serialize())])
+            self.dbapi.commit()
         # Emit after added:
         if emit:
             self.emit(emit, ([tag.handle],))
@@ -1431,6 +1441,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 emit = "media-add"
                 self.dbapi.execute("insert into media values(?, ?, ?);", 
                            [media.handle, media.gramps_id, pickle.dumps(media.serialize())])
+            self.dbapi.commit()
         # Emit after added:
         if emit:
             self.emit(emit, ([media.handle],))
@@ -1706,6 +1717,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             writer.write(filename)
             filename = os.path.join(self._directory, "meta_data.db")
             touch(filename)
+            self.dbapi.close()
 
     def find_backlink_handles(self, handle, include_classes=None):
         ## FIXME
@@ -1907,9 +1919,15 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
              force_bsddb_downgrade=False, 
              force_python_upgrade=False):
         # Run code from directory
-        import sqlite3
-        self.dbapi = sqlite3.connect(':memory:')
-        self.dbapi.row_factory = sqlite3.Row # allows access by name
+        default_settings = {"__file__": 
+                            os.path.join(directory, "default_settings.py")}
+        settings_file = os.path.join(directory, "default_settings.py")
+        with open(settings_file) as f:
+            code = compile(f.read(), settings_file, 'exec')
+            exec(code, globals(), default_settings)
+
+        self.dbapi = default_settings["dbapi"]
+            
         # make sure schema is up to date:
         self.dbapi.execute("""CREATE TABLE IF NOT EXISTS person (
                                     handle    TEXT PRIMARY KEY NOT NULL,
@@ -1990,6 +2008,13 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         _LOG.debug("Write database backend file to 'dbapi'")
         with open(versionpath, "w") as version_file:
             version_file.write("dbapi")
+        # Write default_settings, sqlite.db
+        defaults = os.path.join(os.path.dirname(os.path.abspath(__file__)),
+                                "dbapi_support", "defaults")
+        _LOG.debug("Copy defaults from: " + defaults)
+        for filename in os.listdir(defaults):
+            fullpath = os.path.abspath(os.path.join(defaults, filename))
+            shutil.copy2(fullpath, directory)
 
     def report_bm_change(self):
         """
diff --git a/gramps/plugins/database/dbapi_support/defaults/default_settings.py b/gramps/plugins/database/dbapi_support/defaults/default_settings.py
new file mode 100644
index 000000000..8931c8e89
--- /dev/null
+++ b/gramps/plugins/database/dbapi_support/defaults/default_settings.py
@@ -0,0 +1,7 @@
+import os
+import sqlite3
+
+path_to_db = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                        'sqlite.db')  
+dbapi = sqlite3.connect(path_to_db)
+dbapi.row_factory = sqlite3.Row # allows access by name

From d69f8a6d3c10dda8fe58eacc55896ae39f2d301a Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 23 May 2015 09:54:04 -0400
Subject: [PATCH 088/105] Added support for sort_handles

---
 gramps/plugins/database/dbapi.py | 162 ++++++++++++++++++++++---------
 1 file changed, 114 insertions(+), 48 deletions(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index c99146b6d..3dd90ac49 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -38,16 +38,8 @@ from gramps.gen.db import (PERSON_KEY,
 
 from gramps.gen.utils.id import create_id
 from gramps.gen.lib.researcher import Researcher
-from gramps.gen.lib.mediaobj import MediaObject
-from gramps.gen.lib.person import Person
-from gramps.gen.lib.family import Family
-from gramps.gen.lib.src import Source
-from gramps.gen.lib.citation import Citation
-from gramps.gen.lib.event import Event
-from gramps.gen.lib.place import Place
-from gramps.gen.lib.repo import Repository
-from gramps.gen.lib.note import Note
-from gramps.gen.lib.tag import Tag
+from gramps.gen.lib import (Tag, MediaObject, Person, Family, Source, Citation, Event,
+                            Place, Repository, Note, NameOriginType)
 from gramps.gen.lib.genderstats import GenderStats
 
 _LOG = logging.getLogger(DBLOGNAME)
@@ -761,62 +753,70 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return None
 
     def get_person_handles(self, sort_handles=False):
-        ## Fixme: implement sort
-        cur = self.dbapi.execute("select handle from person;")
+        if sort_handles:
+            cur = self.dbapi.execute("SELECT handle FROM person ORDER BY order_by;")
+        else:
+            cur = self.dbapi.execute("SELECT handle FROM person;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
-    def get_family_handles(self, sort_handles=False):
-        ## Fixme: implement sort
+    def get_family_handles(self):
         cur = self.dbapi.execute("select handle from family;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
-    def get_event_handles(self, sort_handles=False):
-        ## Fixme: implement sort
+    def get_event_handles(self):
         cur = self.dbapi.execute("select handle from event;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_citation_handles(self, sort_handles=False):
-        ## Fixme: implement sort
-        cur = self.dbapi.execute("select handle from citation;")
+        if sort_handles:
+            cur = self.dbapi.execute("select handle from citation ORDER BY order_by;")
+        else:
+            cur = self.dbapi.execute("select handle from citation;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_source_handles(self, sort_handles=False):
-        ## Fixme: implement sort
-        cur = self.dbapi.execute("select handle from source;")
+        if sort_handles:
+            cur = self.dbapi.execute("select handle from source ORDER BY order_by;")
+        else:
+            cur = self.dbapi.execute("select handle from source;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_place_handles(self, sort_handles=False):
-        ## Fixme: implement sort
-        cur = self.dbapi.execute("select handle from place;")
+        if sort_handles:
+            cur = self.dbapi.execute("select handle from place ORDER BY order_by;")
+        else:
+            cur = self.dbapi.execute("select handle from place;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
-    def get_repository_handles(self, sort_handles=False):
-        ## Fixme: implement sort
+    def get_repository_handles(self):
         cur = self.dbapi.execute("select handle from repository;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_media_object_handles(self, sort_handles=False):
-        ## Fixme: implement sort
-        cur = self.dbapi.execute("select handle from media;")
+        if sort_handles:
+            cur = self.dbapi.execute("select handle from media ORDER BY order_by;")
+        else:
+            cur = self.dbapi.execute("select handle from media;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
-    def get_note_handles(self, sort_handles=False):
-        ## Fixme: implement sort
+    def get_note_handles(self):
         cur = self.dbapi.execute("select handle from note;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_tag_handles(self, sort_handles=False):
-        # FIXME: implement sort
-        cur = self.dbapi.execute("select handle from tag;")
+        if sort_handles:
+            cur = self.dbapi.execute("select handle from tag ORDER BY order_by;")
+        else:
+            cur = self.dbapi.execute("select handle from tag;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
@@ -1252,16 +1252,21 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             if person.handle in self.person_map:
                 emit = "person-update"
                 self.dbapi.execute("""UPDATE person SET gramps_id = ?, 
+                                                        order_by = ?,
                                                         blob = ? 
                                                     WHERE handle = ?;""",
                                    [person.gramps_id, 
+                                    self._order_by_person_key(person),
                                     pickle.dumps(person.serialize()),
                                     person.handle])
             else:
                 emit = "person-add"
-                self.dbapi.execute("""insert into person(handle, gramps_id, blob) 
-                                                  values(?, ?, ?);""", 
-                                   [person.handle, person.gramps_id, pickle.dumps(person.serialize())])
+                self.dbapi.execute("""insert into person(handle, order_by, gramps_id, blob) 
+                                                  values(?, ?, ?, ?);""", 
+                                   [person.handle, 
+                                    self._order_by_person_key(person),
+                                    person.gramps_id, 
+                                    pickle.dumps(person.serialize())])
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1294,15 +1299,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             if citation.handle in self.citation_map:
                 emit = "citation-update"
                 self.dbapi.execute("""UPDATE citation SET gramps_id = ?, 
-                                                        blob = ? 
+                                                          order_by = ?,
+                                                          blob = ? 
                                                     WHERE handle = ?;""",
                                    [citation.gramps_id, 
+                                    self._order_by_citation_key(citation),
                                     pickle.dumps(citation.serialize()),
                                     citation.handle])
             else:
                 emit = "citation-add"
-                self.dbapi.execute("insert into citation values(?, ?, ?);", 
-                           [citation.handle, citation.gramps_id, pickle.dumps(citation.serialize())])
+                self.dbapi.execute("insert into citation values(?, ?, ?, ?);", 
+                           [citation.handle, 
+                            self._order_by_citation_key(citation),
+                            citation.gramps_id, 
+                            pickle.dumps(citation.serialize())])
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1314,15 +1324,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             if source.handle in self.source_map:
                 emit = "source-update"
                 self.dbapi.execute("""UPDATE source SET gramps_id = ?, 
+                                                        order_by = ?,
                                                         blob = ? 
                                                     WHERE handle = ?;""",
                                    [source.gramps_id, 
+                                    self._order_by_source_key(source),
                                     pickle.dumps(source.serialize()),
                                     source.handle])
             else:
                 emit = "source-add"
-                self.dbapi.execute("insert into source values(?, ?, ?);", 
-                           [source.handle, source.gramps_id, pickle.dumps(source.serialize())])
+                self.dbapi.execute("insert into source values(?, ?, ?, ?);", 
+                           [source.handle, 
+                            self._order_by_source_key(source),
+                            source.gramps_id, 
+                            pickle.dumps(source.serialize())])
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1374,15 +1389,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             if place.handle in self.place_map:
                 emit = "place-update"
                 self.dbapi.execute("""UPDATE place SET gramps_id = ?, 
-                                                        blob = ? 
+                                                       order_by = ?,
+                                                       blob = ? 
                                                     WHERE handle = ?;""",
                                    [place.gramps_id, 
+                                    self._order_by_place_key(place),
                                     pickle.dumps(place.serialize()),
                                     place.handle])
             else:
                 emit = "place-add"
-                self.dbapi.execute("insert into place values(?, ?, ?);", 
-                           [place.handle, place.gramps_id, pickle.dumps(place.serialize())])
+                self.dbapi.execute("insert into place values(?, ?, ?, ?);", 
+                           [place.handle, 
+                            self._order_by_place_key(place),
+                            place.gramps_id, 
+                            pickle.dumps(place.serialize())])
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1402,7 +1422,9 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             else:
                 emit = "event-add"
                 self.dbapi.execute("insert into event values(?, ?, ?);", 
-                           [event.handle, event.gramps_id, pickle.dumps(event.serialize())])
+                           [event.handle, 
+                            event.gramps_id, 
+                            pickle.dumps(event.serialize())])
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1413,14 +1435,18 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if True or not trans.batch:
             if tag.handle in self.tag_map:
                 emit = "tag-update"
-                self.dbapi.execute("""UPDATE tag SET blob = ? 
+                self.dbapi.execute("""UPDATE tag SET blob = ?,
+                                                     order_by = ?
                                              WHERE handle = ?;""",
                                    [pickle.dumps(tag.serialize()),
+                                    self._order_by_tag_key(tag),
                                     tag.handle])
             else:
                 emit = "tag-add"
-                self.dbapi.execute("insert into tag values(?, ?);", 
-                           [tag.handle, pickle.dumps(tag.serialize())])
+                self.dbapi.execute("insert into tag values(?, ?, ?);", 
+                           [tag.handle, 
+                            self._order_by_tag_key(tag),
+                            pickle.dumps(tag.serialize())])
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1432,15 +1458,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             if media.handle in self.media_map:
                 emit = "media-update"
                 self.dbapi.execute("""UPDATE media SET gramps_id = ?, 
-                                                        blob = ? 
+                                                       order_by = ?,
+                                                       blob = ? 
                                                     WHERE handle = ?;""",
                                    [media.gramps_id, 
+                                    self._order_by_media_key(media),
                                     pickle.dumps(media.serialize()),
                                     media.handle])
             else:
                 emit = "media-add"
-                self.dbapi.execute("insert into media values(?, ?, ?);", 
-                           [media.handle, media.gramps_id, pickle.dumps(media.serialize())])
+                self.dbapi.execute("insert into media values(?, ?, ?, ?);", 
+                           [media.handle, 
+                            self._order_by_media_key(media),
+                            media.gramps_id, 
+                            pickle.dumps(media.serialize())])
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1931,6 +1962,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # make sure schema is up to date:
         self.dbapi.execute("""CREATE TABLE IF NOT EXISTS person (
                                     handle    TEXT PRIMARY KEY NOT NULL,
+                                    order_by  TEXT             ,
                                     gramps_id TEXT             ,
                                     blob      TEXT
         );""")
@@ -1941,11 +1973,13 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         );""")
         self.dbapi.execute("""CREATE TABLE IF NOT EXISTS source (
                                     handle    TEXT PRIMARY KEY NOT NULL,
+                                    order_by  TEXT             ,
                                     gramps_id TEXT             ,
                                     blob      TEXT
         );""")
         self.dbapi.execute("""CREATE TABLE IF NOT EXISTS citation (
                                     handle    TEXT PRIMARY KEY NOT NULL,
+                                    order_by  TEXT             ,
                                     gramps_id TEXT             ,
                                     blob      TEXT
         );""")
@@ -1956,11 +1990,13 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         );""")
         self.dbapi.execute("""CREATE TABLE IF NOT EXISTS media (
                                     handle    TEXT PRIMARY KEY NOT NULL,
+                                    order_by  TEXT             ,
                                     gramps_id TEXT             ,
                                     blob      TEXT
         );""")
         self.dbapi.execute("""CREATE TABLE IF NOT EXISTS place (
                                     handle    TEXT PRIMARY KEY NOT NULL,
+                                    order_by  TEXT             ,
                                     gramps_id TEXT             ,
                                     blob      TEXT
         );""")
@@ -1976,6 +2012,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         );""")
         self.dbapi.execute("""CREATE TABLE IF NOT EXISTS tag (
                                     handle    TEXT PRIMARY KEY NOT NULL,
+                                    order_by  TEXT             ,
                                     blob      TEXT
         );""")
 
@@ -2254,3 +2291,32 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if row:
             return pickle.loads(row[0])
 
+    def _order_by_person_key(self, person):
+        """
+        All non pa/matronymic surnames are used in indexing.
+        pa/matronymic not as they change for every generation!
+        returns a byte string
+        """
+        if person.primary_name and person.primary_name.surname_list:
+            order_by = " ".join([x.surname for x in person.primary_name.surname_list if not 
+                                 (int(x.origintype) in [NameOriginType.PATRONYMIC, 
+                                                        NameOriginType.MATRONYMIC]) ])
+        else:
+            order_by = ""
+        return glocale.sort_key(order_by)
+
+    def _order_by_place_key(self, place):
+        return glocale.sort_key(place.title)
+
+    def _order_by_source_key(self, source):
+        return glocale.sort_key(source.title)
+
+    def _order_by_citation_key(self, citation):
+        return glocale.sort_key(citation.page)
+
+    def _order_by_media_key(self, media):
+        return glocale.sort_key(media.desc)
+
+    def _order_by_tag_key(self, tag):
+        return glocale.sort_key(tag.get_name())
+

From 8be61709b8d74266ce57cbbc36314ccce1096ad8 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 23 May 2015 10:07:57 -0400
Subject: [PATCH 089/105] Now using batch transactions

---
 gramps/plugins/database/dbapi.py | 328 +++++++++++++++----------------
 1 file changed, 163 insertions(+), 165 deletions(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index 3dd90ac49..774a4bdda 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -495,16 +495,14 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return None
 
     def transaction_commit(self, txn):
-        ## FIXME
-        pass
+        self.dbapi.commit()
 
     def get_undodb(self):
         ## FIXME
         return None
 
     def transaction_abort(self, txn):
-        ## FIXME
-        pass
+        self.dbapi.rollback()
 
     @staticmethod
     def _validated_id_prefix(val, default):
@@ -1248,25 +1246,25 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def commit_person(self, person, trans, change_time=None):
         emit = None
-        if True or not trans.batch:
-            if person.handle in self.person_map:
-                emit = "person-update"
-                self.dbapi.execute("""UPDATE person SET gramps_id = ?, 
-                                                        order_by = ?,
-                                                        blob = ? 
-                                                    WHERE handle = ?;""",
-                                   [person.gramps_id, 
-                                    self._order_by_person_key(person),
-                                    pickle.dumps(person.serialize()),
-                                    person.handle])
-            else:
-                emit = "person-add"
-                self.dbapi.execute("""insert into person(handle, order_by, gramps_id, blob) 
-                                                  values(?, ?, ?, ?);""", 
-                                   [person.handle, 
-                                    self._order_by_person_key(person),
-                                    person.gramps_id, 
-                                    pickle.dumps(person.serialize())])
+        if person.handle in self.person_map:
+            emit = "person-update"
+            self.dbapi.execute("""UPDATE person SET gramps_id = ?, 
+                                                    order_by = ?,
+                                                    blob = ? 
+                                                WHERE handle = ?;""",
+                               [person.gramps_id, 
+                                self._order_by_person_key(person),
+                                pickle.dumps(person.serialize()),
+                                person.handle])
+        else:
+            emit = "person-add"
+            self.dbapi.execute("""insert into person(handle, order_by, gramps_id, blob) 
+                                              values(?, ?, ?, ?);""", 
+                               [person.handle, 
+                                self._order_by_person_key(person),
+                                person.gramps_id, 
+                                pickle.dumps(person.serialize())])
+        if not trans.batch:
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1274,20 +1272,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def commit_family(self, family, trans, change_time=None):
         emit = None
-        if True or not trans.batch:
-            if family.handle in self.family_map:
-                emit = "family-update"
-                self.dbapi.execute("""UPDATE family SET gramps_id = ?, 
-                                                        blob = ? 
-                                                    WHERE handle = ?;""",
-                                   [family.gramps_id, 
-                                    pickle.dumps(family.serialize()),
-                                    family.handle])
-            else:
-                emit = "family-add"
-                self.dbapi.execute("insert into family values(?, ?, ?);", 
-                                   [family.handle, family.gramps_id, 
-                                    pickle.dumps(family.serialize())])
+        if family.handle in self.family_map:
+            emit = "family-update"
+            self.dbapi.execute("""UPDATE family SET gramps_id = ?, 
+                                                    blob = ? 
+                                                WHERE handle = ?;""",
+                               [family.gramps_id, 
+                                pickle.dumps(family.serialize()),
+                                family.handle])
+        else:
+            emit = "family-add"
+            self.dbapi.execute("insert into family values(?, ?, ?);", 
+                               [family.handle, family.gramps_id, 
+                                pickle.dumps(family.serialize())])
+        if not trans.batch:
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1295,24 +1293,24 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def commit_citation(self, citation, trans, change_time=None):
         emit = None
-        if True or not trans.batch:
-            if citation.handle in self.citation_map:
-                emit = "citation-update"
-                self.dbapi.execute("""UPDATE citation SET gramps_id = ?, 
-                                                          order_by = ?,
-                                                          blob = ? 
-                                                    WHERE handle = ?;""",
-                                   [citation.gramps_id, 
-                                    self._order_by_citation_key(citation),
-                                    pickle.dumps(citation.serialize()),
-                                    citation.handle])
-            else:
-                emit = "citation-add"
-                self.dbapi.execute("insert into citation values(?, ?, ?, ?);", 
-                           [citation.handle, 
-                            self._order_by_citation_key(citation),
-                            citation.gramps_id, 
-                            pickle.dumps(citation.serialize())])
+        if citation.handle in self.citation_map:
+            emit = "citation-update"
+            self.dbapi.execute("""UPDATE citation SET gramps_id = ?, 
+                                                      order_by = ?,
+                                                      blob = ? 
+                                                WHERE handle = ?;""",
+                               [citation.gramps_id, 
+                                self._order_by_citation_key(citation),
+                                pickle.dumps(citation.serialize()),
+                                citation.handle])
+        else:
+            emit = "citation-add"
+            self.dbapi.execute("insert into citation values(?, ?, ?, ?);", 
+                       [citation.handle, 
+                        self._order_by_citation_key(citation),
+                        citation.gramps_id, 
+                        pickle.dumps(citation.serialize())])
+        if not trans.batch:
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1320,24 +1318,24 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def commit_source(self, source, trans, change_time=None):
         emit = None
-        if True or not trans.batch:
-            if source.handle in self.source_map:
-                emit = "source-update"
-                self.dbapi.execute("""UPDATE source SET gramps_id = ?, 
-                                                        order_by = ?,
-                                                        blob = ? 
-                                                    WHERE handle = ?;""",
-                                   [source.gramps_id, 
-                                    self._order_by_source_key(source),
-                                    pickle.dumps(source.serialize()),
-                                    source.handle])
-            else:
-                emit = "source-add"
-                self.dbapi.execute("insert into source values(?, ?, ?, ?);", 
-                           [source.handle, 
-                            self._order_by_source_key(source),
-                            source.gramps_id, 
-                            pickle.dumps(source.serialize())])
+        if source.handle in self.source_map:
+            emit = "source-update"
+            self.dbapi.execute("""UPDATE source SET gramps_id = ?, 
+                                                    order_by = ?,
+                                                    blob = ? 
+                                                WHERE handle = ?;""",
+                               [source.gramps_id, 
+                                self._order_by_source_key(source),
+                                pickle.dumps(source.serialize()),
+                                source.handle])
+        else:
+            emit = "source-add"
+            self.dbapi.execute("insert into source values(?, ?, ?, ?);", 
+                       [source.handle, 
+                        self._order_by_source_key(source),
+                        source.gramps_id, 
+                        pickle.dumps(source.serialize())])
+        if not trans.batch:
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1345,19 +1343,19 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def commit_repository(self, repository, trans, change_time=None):
         emit = None
-        if True or not trans.batch:
-            if repository.handle in self.repository_map:
-                emit = "repository-update"
-                self.dbapi.execute("""UPDATE repository SET gramps_id = ?, 
-                                                        blob = ? 
-                                                    WHERE handle = ?;""",
-                                   [repository.gramps_id, 
-                                    pickle.dumps(repository.serialize()),
-                                    repository.handle])
-            else:
-                emit = "repository-add"
-                self.dbapi.execute("insert into repository values(?, ?, ?);", 
-                           [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())])
+        if repository.handle in self.repository_map:
+            emit = "repository-update"
+            self.dbapi.execute("""UPDATE repository SET gramps_id = ?, 
+                                                    blob = ? 
+                                                WHERE handle = ?;""",
+                               [repository.gramps_id, 
+                                pickle.dumps(repository.serialize()),
+                                repository.handle])
+        else:
+            emit = "repository-add"
+            self.dbapi.execute("insert into repository values(?, ?, ?);", 
+                       [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())])
+        if not trans.batch:
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1365,19 +1363,19 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def commit_note(self, note, trans, change_time=None):
         emit = None
-        if True or not trans.batch:
-            if note.handle in self.note_map:
-                emit = "note-update"
-                self.dbapi.execute("""UPDATE note SET gramps_id = ?, 
-                                                        blob = ? 
-                                                    WHERE handle = ?;""",
-                                   [note.gramps_id, 
-                                    pickle.dumps(note.serialize()),
-                                    note.handle])
-            else:
-                emit = "note-add"
-                self.dbapi.execute("insert into note values(?, ?, ?);", 
-                           [note.handle, note.gramps_id, pickle.dumps(note.serialize())])
+        if note.handle in self.note_map:
+            emit = "note-update"
+            self.dbapi.execute("""UPDATE note SET gramps_id = ?, 
+                                                    blob = ? 
+                                                WHERE handle = ?;""",
+                               [note.gramps_id, 
+                                pickle.dumps(note.serialize()),
+                                note.handle])
+        else:
+            emit = "note-add"
+            self.dbapi.execute("insert into note values(?, ?, ?);", 
+                       [note.handle, note.gramps_id, pickle.dumps(note.serialize())])
+        if not trans.batch:
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1385,24 +1383,24 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def commit_place(self, place, trans, change_time=None):
         emit = None
-        if True or not trans.batch:
-            if place.handle in self.place_map:
-                emit = "place-update"
-                self.dbapi.execute("""UPDATE place SET gramps_id = ?, 
-                                                       order_by = ?,
-                                                       blob = ? 
-                                                    WHERE handle = ?;""",
-                                   [place.gramps_id, 
-                                    self._order_by_place_key(place),
-                                    pickle.dumps(place.serialize()),
-                                    place.handle])
-            else:
-                emit = "place-add"
-                self.dbapi.execute("insert into place values(?, ?, ?, ?);", 
-                           [place.handle, 
-                            self._order_by_place_key(place),
-                            place.gramps_id, 
-                            pickle.dumps(place.serialize())])
+        if place.handle in self.place_map:
+            emit = "place-update"
+            self.dbapi.execute("""UPDATE place SET gramps_id = ?, 
+                                                   order_by = ?,
+                                                   blob = ? 
+                                                WHERE handle = ?;""",
+                               [place.gramps_id, 
+                                self._order_by_place_key(place),
+                                pickle.dumps(place.serialize()),
+                                place.handle])
+        else:
+            emit = "place-add"
+            self.dbapi.execute("insert into place values(?, ?, ?, ?);", 
+                       [place.handle, 
+                        self._order_by_place_key(place),
+                        place.gramps_id, 
+                        pickle.dumps(place.serialize())])
+        if not trans.batch:
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1410,21 +1408,21 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def commit_event(self, event, trans, change_time=None):
         emit = None
-        if True or not trans.batch:
-            if event.handle in self.event_map:
-                emit = "event-update"
-                self.dbapi.execute("""UPDATE event SET gramps_id = ?, 
-                                                        blob = ? 
-                                                    WHERE handle = ?;""",
-                                   [event.gramps_id, 
-                                    pickle.dumps(event.serialize()),
-                                    event.handle])
-            else:
-                emit = "event-add"
-                self.dbapi.execute("insert into event values(?, ?, ?);", 
-                           [event.handle, 
-                            event.gramps_id, 
-                            pickle.dumps(event.serialize())])
+        if event.handle in self.event_map:
+            emit = "event-update"
+            self.dbapi.execute("""UPDATE event SET gramps_id = ?, 
+                                                    blob = ? 
+                                                WHERE handle = ?;""",
+                               [event.gramps_id, 
+                                pickle.dumps(event.serialize()),
+                                event.handle])
+        else:
+            emit = "event-add"
+            self.dbapi.execute("insert into event values(?, ?, ?);", 
+                       [event.handle, 
+                        event.gramps_id, 
+                        pickle.dumps(event.serialize())])
+        if not trans.batch:
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1432,21 +1430,21 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def commit_tag(self, tag, trans, change_time=None):
         emit = None
-        if True or not trans.batch:
-            if tag.handle in self.tag_map:
-                emit = "tag-update"
-                self.dbapi.execute("""UPDATE tag SET blob = ?,
-                                                     order_by = ?
-                                             WHERE handle = ?;""",
-                                   [pickle.dumps(tag.serialize()),
-                                    self._order_by_tag_key(tag),
-                                    tag.handle])
-            else:
-                emit = "tag-add"
-                self.dbapi.execute("insert into tag values(?, ?, ?);", 
-                           [tag.handle, 
-                            self._order_by_tag_key(tag),
-                            pickle.dumps(tag.serialize())])
+        if tag.handle in self.tag_map:
+            emit = "tag-update"
+            self.dbapi.execute("""UPDATE tag SET blob = ?,
+                                                 order_by = ?
+                                         WHERE handle = ?;""",
+                               [pickle.dumps(tag.serialize()),
+                                self._order_by_tag_key(tag),
+                                tag.handle])
+        else:
+            emit = "tag-add"
+            self.dbapi.execute("insert into tag values(?, ?, ?);", 
+                       [tag.handle, 
+                        self._order_by_tag_key(tag),
+                        pickle.dumps(tag.serialize())])
+        if not trans.batch:
             self.dbapi.commit()
         # Emit after added:
         if emit:
@@ -1454,24 +1452,24 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def commit_media_object(self, media, trans, change_time=None):
         emit = None
-        if True or not trans.batch:
-            if media.handle in self.media_map:
-                emit = "media-update"
-                self.dbapi.execute("""UPDATE media SET gramps_id = ?, 
-                                                       order_by = ?,
-                                                       blob = ? 
-                                                    WHERE handle = ?;""",
-                                   [media.gramps_id, 
-                                    self._order_by_media_key(media),
-                                    pickle.dumps(media.serialize()),
-                                    media.handle])
-            else:
-                emit = "media-add"
-                self.dbapi.execute("insert into media values(?, ?, ?, ?);", 
-                           [media.handle, 
-                            self._order_by_media_key(media),
-                            media.gramps_id, 
-                            pickle.dumps(media.serialize())])
+        if media.handle in self.media_map:
+            emit = "media-update"
+            self.dbapi.execute("""UPDATE media SET gramps_id = ?, 
+                                                   order_by = ?,
+                                                   blob = ? 
+                                                WHERE handle = ?;""",
+                               [media.gramps_id, 
+                                self._order_by_media_key(media),
+                                pickle.dumps(media.serialize()),
+                                media.handle])
+        else:
+            emit = "media-add"
+            self.dbapi.execute("insert into media values(?, ?, ?, ?);", 
+                       [media.handle, 
+                        self._order_by_media_key(media),
+                        media.gramps_id, 
+                        pickle.dumps(media.serialize())])
+        if not trans.batch:
             self.dbapi.commit()
         # Emit after added:
         if emit:

From a6f7093baeb5360b3568149a837c537e926fe8b3 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 23 May 2015 10:27:49 -0400
Subject: [PATCH 090/105] Added indices on order_by fields

---
 gramps/plugins/database/dbapi.py | 23 ++++++++++++++++++++++-
 1 file changed, 22 insertions(+), 1 deletion(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index 774a4bdda..436ba87d5 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -1490,7 +1490,9 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return list(key2table[obj_key].keys())
 
     def transaction_begin(self, transaction):
-        ## FIXME
+        """
+        Transactions are handled automatically by the db layer.
+        """
         return 
 
     def set_researcher(self, owner):
@@ -2013,6 +2015,25 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                     order_by  TEXT             ,
                                     blob      TEXT
         );""")
+        ## Indices:
+        self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
+                                  order_by ON person (order_by);
+        """)
+        self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
+                                  order_by ON source (order_by);
+        """)
+        self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
+                                  order_by ON citation (order_by);
+        """)
+        self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
+                                  order_by ON media (order_by);
+        """)
+        self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
+                                  order_by ON place (order_by);
+        """)
+        self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
+                                  order_by ON tag (order_by);
+        """)
 
     def redo(self, update_history=True):
         ## FIXME

From 1871c6ced4ba896b8be0fff4ec4a31d851f66ee5 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 23 May 2015 14:52:43 -0400
Subject: [PATCH 091/105] Basics for back references now work, although doesn't
 update with edits yet

---
 gramps/plugins/database/dbapi.py | 145 +++++++++++++++++--------------
 1 file changed, 79 insertions(+), 66 deletions(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index 436ba87d5..31e50534c 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -20,7 +20,8 @@ import gramps
 from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
 from gramps.gen.db import (DbReadBase, DbWriteBase, DbTxn, 
-                           KEY_TO_NAME_MAP, KEY_TO_CLASS_MAP)
+                           KEY_TO_NAME_MAP, KEY_TO_CLASS_MAP, 
+                           CLASS_TO_KEY_MAP)
 from gramps.gen.utils.callback import Callback
 from gramps.gen.updatecallback import UpdateCallback
 from gramps.gen.db.undoredo import DbUndo
@@ -1245,6 +1246,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return obj.handle
 
     def commit_person(self, person, trans, change_time=None):
+        ## FIXME: update reference, for back references
         emit = None
         if person.handle in self.person_map:
             emit = "person-update"
@@ -1271,6 +1273,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.emit(emit, ([person.handle],))
 
     def commit_family(self, family, trans, change_time=None):
+        ## FIXME: update reference, for back references
         emit = None
         if family.handle in self.family_map:
             emit = "family-update"
@@ -1292,6 +1295,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.emit(emit, ([family.handle],))
 
     def commit_citation(self, citation, trans, change_time=None):
+        ## FIXME: update reference, for back references
         emit = None
         if citation.handle in self.citation_map:
             emit = "citation-update"
@@ -1317,6 +1321,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.emit(emit, ([citation.handle],))
 
     def commit_source(self, source, trans, change_time=None):
+        ## FIXME: update reference, for back references
         emit = None
         if source.handle in self.source_map:
             emit = "source-update"
@@ -1342,6 +1347,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.emit(emit, ([source.handle],))
 
     def commit_repository(self, repository, trans, change_time=None):
+        ## FIXME: update reference, for back references
         emit = None
         if repository.handle in self.repository_map:
             emit = "repository-update"
@@ -1362,6 +1368,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.emit(emit, ([repository.handle],))
 
     def commit_note(self, note, trans, change_time=None):
+        ## FIXME: update reference, for back references
         emit = None
         if note.handle in self.note_map:
             emit = "note-update"
@@ -1382,6 +1389,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.emit(emit, ([note.handle],))
 
     def commit_place(self, place, trans, change_time=None):
+        ## FIXME: update reference, for back references
         emit = None
         if place.handle in self.place_map:
             emit = "place-update"
@@ -1407,6 +1415,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.emit(emit, ([place.handle],))
 
     def commit_event(self, event, trans, change_time=None):
+        ## FIXME: update reference, for back references
         emit = None
         if event.handle in self.event_map:
             emit = "event-update"
@@ -1429,6 +1438,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.emit(emit, ([event.handle],))
 
     def commit_tag(self, tag, trans, change_time=None):
+        ## FIXME: update reference, for back references
         emit = None
         if tag.handle in self.tag_map:
             emit = "tag-update"
@@ -1451,6 +1461,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.emit(emit, ([tag.handle],))
 
     def commit_media_object(self, media, trans, change_time=None):
+        ## FIXME: update reference, for back references
         emit = None
         if media.handle in self.media_map:
             emit = "media-update"
@@ -1675,64 +1686,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                [handle])
             self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],))
 
-    def delete_primary_from_reference_map(self, handle, transaction, txn=None):
-        """
-        Remove all references to the primary object from the reference_map.
-        handle should be utf-8
-        """
-        primary_cur = self.get_reference_map_primary_cursor()
-
-        try:
-            ret = primary_cur.set(handle)
-        except:
-            ret = None
-        
-        remove_list = set()
-        while (ret is not None):
-            (key, data) = ret
-            
-            # data values are of the form:
-            #   ((primary_object_class_name, primary_object_handle),
-            #    (referenced_object_class_name, referenced_object_handle))
-            
-            # so we need the second tuple give us a reference that we can
-            # combine with the primary_handle to get the main key.
-            main_key = (handle.decode('utf-8'), pickle.loads(data)[1][1])
-            
-            # The trick is not to remove while inside the cursor,
-            # but collect them all and remove after the cursor is closed
-            remove_list.add(main_key)
-
-            ret = primary_cur.next_dup()
-
-        primary_cur.close()
-
-        # Now that the cursor is closed, we can remove things
-        for main_key in remove_list:
-            self.__remove_reference(main_key, transaction, txn)
-
-    def __remove_reference(self, key, transaction, txn):
-        """
-        Remove the reference specified by the key, preserving the change in 
-        the passed transaction.
-        """
-        if isinstance(key, tuple):
-            #create a byte string key, first validity check in python 3!
-            for val in key:
-                if isinstance(val, bytes):
-                    raise DbError(_('An attempt is made to save a reference key '
-                        'which is partly bytecode, this is not allowed.\n'
-                        'Key is %s') % str(key))
-            key = str(key)
-        if isinstance(key, str):
-            key = key.encode('utf-8')
-        if not self.readonly:
-            if not transaction.batch:
-                old_data = self.reference_map.get(key, txn=txn)
-                transaction.add(REFERENCE_KEY, TXNDEL, key, old_data, None)
-                #transaction.reference_del.append(str(key))
-            self.reference_map.delete(key, txn=txn)
-
     ## Missing:
 
     def backup(self):
@@ -1751,8 +1704,28 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.dbapi.close()
 
     def find_backlink_handles(self, handle, include_classes=None):
-        ## FIXME
-        return []
+        """
+        Find all objects that hold a reference to the object handle.
+        
+        Returns an interator over a list of (class_name, handle) tuples.
+
+        :param handle: handle of the object to search for.
+        :type handle: database handle
+        :param include_classes: list of class names to include in the results.
+            Default: None means include all classes.
+        :type include_classes: list of class names
+
+        Note that this is a generator function, it returns a iterator for
+        use in loops. If you want a list of the results use::
+
+            result_list = list(find_backlink_handles(handle))
+        """
+        cur = self.dbapi.execute("SELECT * from reference WHERE ref_handle = ?;",
+                                 [handle])
+        rows = cur.fetchall()
+        for row in rows:
+            if (include_classes is None) or (row["obj_class"] in include_classes):
+                yield (row["obj_class"], row["obj_handle"])
 
     def find_initial_person(self):
         items = self.person_map.keys()
@@ -2015,6 +1988,13 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                     order_by  TEXT             ,
                                     blob      TEXT
         );""")
+        # Reference
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS reference (
+                                    obj_handle    TEXT,
+                                    obj_class     TEXT,
+                                    ref_handle    TEXT,
+                                    ref_class     TEXT
+        );""")
         ## Indices:
         self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
                                   order_by ON person (order_by);
@@ -2034,6 +2014,9 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
                                   order_by ON tag (order_by);
         """)
+        self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
+                                  ref_handle ON reference (ref_handle);
+        """)
 
     def redo(self, update_history=True):
         ## FIXME
@@ -2111,13 +2094,43 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             name = None
         return name
 
-    def reindex_reference_map(self):
-        ## FIXME
-        pass
+    def reindex_reference_map(self, callback):
+        callback(4)
+        self.dbapi.execute("DELETE FROM reference;")
+        primary_table = (
+            (self.get_person_cursor, Person),
+            (self.get_family_cursor, Family),
+            (self.get_event_cursor, Event),
+            (self.get_place_cursor, Place),
+            (self.get_source_cursor, Source),
+            (self.get_citation_cursor, Citation),
+            (self.get_media_cursor, MediaObject),
+            (self.get_repository_cursor, Repository),
+            (self.get_note_cursor, Note),
+            (self.get_tag_cursor, Tag),
+        )
+        # Now we use the functions and classes defined above
+        # to loop through each of the primary object tables.
+        for cursor_func, class_func in primary_table:
+            logging.info("Rebuilding %s reference map" %
+                         class_func.__name__)
+            with cursor_func() as cursor:
+                for found_handle, val in cursor:
+                    obj = class_func.create(val)
+                    references = set(obj.get_referenced_handles_recursively())
+                    # handle addition of new references
+                    for (ref_class_name, ref_handle) in references:
+                        self.dbapi.execute("INSERT into reference VALUES(?, ?, ?, ?);",
+                                           [obj.handle, 
+                                            obj.__class__.__name__,
+                                            ref_handle, 
+                                            ref_class_name])
+                                            
+        self.dbapi.commit()
+        callback(5)
 
     def rebuild_secondary(self, update):
-        ## FIXME
-        pass
+        self.reindex_reference_map(update)
 
     def prepare_import(self):
         """

From 9274d74dc134a8e9fa4956a2dd6bd88647322e2a Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 23 May 2015 20:25:04 -0400
Subject: [PATCH 092/105] WIP: name_group; clean up of SQL

---
 gramps/plugins/database/dbapi.py | 89 ++++++++++++++++++++------------
 1 file changed, 56 insertions(+), 33 deletions(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index 31e50534c..d501dbdd3 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -462,7 +462,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                  contains_func="has_gramps_id_func")
         self.tag_map  = Map(Table(self._tables["Tag"]))
         self.metadata   = Map(Table({"cursor_func": lambda: MetaCursor()}))
-        self.name_group = {}
+        ## FIXME: add appropriate methods:
+        self.name_group = Map(Table({"handles_func": self.get_name_group_keys,  # keys
+                                     "has_handle_func": self.has_name_group_key, # key in table 
+                                     "cursor_func": None, # create a cursor, values
+                                     "add_func": self.set_name_group_mapping, # add a key, value
+                                     "count_func": None})) # how many items?
         self.undo_callback = None
         self.redo_callback = None
         self.undo_history_callback = None
@@ -746,10 +751,16 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return None
 
     def get_name_group_keys(self):
-        return []
+        cur = self.dbapi.execute("select name from name_group order by name;")
+        rows = cur.fetchall()
+        return [row[0] for row in rows]
 
     def get_name_group_mapping(self, key):
-        return None
+        cur = self.dbapi.execute("select grouping from name_group where name = ?;", 
+                                 [key])
+        row = cur.fetchone()
+        if row:
+            return row[0]
 
     def get_person_handles(self, sort_handles=False):
         if sort_handles:
@@ -1096,12 +1107,22 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return handle in self.media_map
 
     def has_name_group_key(self, key):
-        # FIXME:
-        return False
+        cur = self.dbapi.execute("select grouping from name_group where name = ?;", 
+                                 [key])
+        row = cur.fetchone()
+        return True if row else False
 
-    def set_name_group_mapping(self, key, value):
-        # FIXME:
-        pass
+    def set_name_group_mapping(self, name, grouping):
+        sname = name.encode("utf-8")
+        cur = self.dbapi.execute("SELECT * from name_group where name = ?;", 
+                                 [sname])
+        row = cur.fetchone()
+        if row:
+            cur = self.dbapi.execute("DELETE from name_group where name = ?;", 
+                                     [sname])
+        cur = self.dbapi.execute("INSERT into name_group (name, grouping) VALUES(?, ?);",
+                                 [sname, grouping])
+        self.dbapi.commit()
 
     def set_default_person_handle(self, handle):
         ## FIXME
@@ -1260,8 +1281,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 person.handle])
         else:
             emit = "person-add"
-            self.dbapi.execute("""insert into person(handle, order_by, gramps_id, blob) 
-                                              values(?, ?, ?, ?);""", 
+            self.dbapi.execute("""insert into person (handle, order_by, gramps_id, blob)
+                            values(?, ?, ?, ?);""", 
                                [person.handle, 
                                 self._order_by_person_key(person),
                                 person.gramps_id, 
@@ -1285,7 +1306,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 family.handle])
         else:
             emit = "family-add"
-            self.dbapi.execute("insert into family values(?, ?, ?);", 
+            self.dbapi.execute("""insert into family (handle, gramps_id, blob)
+                    values(?, ?, ?);""", 
                                [family.handle, family.gramps_id, 
                                 pickle.dumps(family.serialize())])
         if not trans.batch:
@@ -1309,7 +1331,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 citation.handle])
         else:
             emit = "citation-add"
-            self.dbapi.execute("insert into citation values(?, ?, ?, ?);", 
+            self.dbapi.execute("""insert into citation (handle, order_by, gramps_id, blob)
+                     values(?, ?, ?, ?);""", 
                        [citation.handle, 
                         self._order_by_citation_key(citation),
                         citation.gramps_id, 
@@ -1335,7 +1358,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 source.handle])
         else:
             emit = "source-add"
-            self.dbapi.execute("insert into source values(?, ?, ?, ?);", 
+            self.dbapi.execute("""insert into source (handle, order_by, gramps_id, blob)
+                    values(?, ?, ?, ?);""", 
                        [source.handle, 
                         self._order_by_source_key(source),
                         source.gramps_id, 
@@ -1359,7 +1383,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 repository.handle])
         else:
             emit = "repository-add"
-            self.dbapi.execute("insert into repository values(?, ?, ?);", 
+            self.dbapi.execute("""insert into repository (handle, gramps_id, blob)
+                     values(?, ?, ?);""", 
                        [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())])
         if not trans.batch:
             self.dbapi.commit()
@@ -1380,7 +1405,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 note.handle])
         else:
             emit = "note-add"
-            self.dbapi.execute("insert into note values(?, ?, ?);", 
+            self.dbapi.execute("""insert into note (handle, gramps_id, blob)
+                     values(?, ?, ?);""", 
                        [note.handle, note.gramps_id, pickle.dumps(note.serialize())])
         if not trans.batch:
             self.dbapi.commit()
@@ -1403,7 +1429,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 place.handle])
         else:
             emit = "place-add"
-            self.dbapi.execute("insert into place values(?, ?, ?, ?);", 
+            self.dbapi.execute("""insert into place (handle, order_by, gramps_id, blob)
+                    values(?, ?, ?, ?);""", 
                        [place.handle, 
                         self._order_by_place_key(place),
                         place.gramps_id, 
@@ -1427,7 +1454,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 event.handle])
         else:
             emit = "event-add"
-            self.dbapi.execute("insert into event values(?, ?, ?);", 
+            self.dbapi.execute("""insert into event (handle, gramps_id, blob)
+                  values(?, ?, ?);""", 
                        [event.handle, 
                         event.gramps_id, 
                         pickle.dumps(event.serialize())])
@@ -1450,7 +1478,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 tag.handle])
         else:
             emit = "tag-add"
-            self.dbapi.execute("insert into tag values(?, ?, ?);", 
+            self.dbapi.execute("""insert into tag (handle, order_by, blob)
+                  values(?, ?, ?);""", 
                        [tag.handle, 
                         self._order_by_tag_key(tag),
                         pickle.dumps(tag.serialize())])
@@ -1475,7 +1504,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 media.handle])
         else:
             emit = "media-add"
-            self.dbapi.execute("insert into media values(?, ?, ?, ?);", 
+            self.dbapi.execute("""insert into media (handle, order_by, gramps_id, blob)
+                  values(?, ?, ?, ?);""", 
                        [media.handle, 
                         self._order_by_media_key(media),
                         media.gramps_id, 
@@ -1747,18 +1777,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def get_citation_bookmarks(self):
         return self.citation_bookmarks
 
-    def get_cursor(self, table, txn=None, update=False, commit=False):
-        ## FIXME
-        ## called from a complete find_back_ref
-        pass
-
-    # cursors for lookups in the reference_map for back reference
-    # lookups. The reference_map has three indexes:
-    # the main index: a tuple of (primary_handle, referenced_handle)
-    # the primary_handle index: the primary_handle
-    # the referenced_handle index: the referenced_handle
-    # the main index is unique, the others allow duplicate entries.
-
     def get_default_handle(self):
         items = self.person_map.keys()
         if len(items) > 0:
@@ -1988,13 +2006,17 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                     order_by  TEXT             ,
                                     blob      TEXT
         );""")
-        # Reference
+        # Secondary:
         self.dbapi.execute("""CREATE TABLE IF NOT EXISTS reference (
                                     obj_handle    TEXT,
                                     obj_class     TEXT,
                                     ref_handle    TEXT,
                                     ref_class     TEXT
         );""")
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS name_group (
+                                    name     TEXT PRIMARY KEY NOT NULL,
+                                    grouping TEXT
+        );""")
         ## Indices:
         self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
                                   order_by ON person (order_by);
@@ -2120,7 +2142,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                     references = set(obj.get_referenced_handles_recursively())
                     # handle addition of new references
                     for (ref_class_name, ref_handle) in references:
-                        self.dbapi.execute("INSERT into reference VALUES(?, ?, ?, ?);",
+                        self.dbapi.execute("""INSERT into reference (obj_handle, obj_class, ref_handle, ref_class)
+                                                 VALUES(?, ?, ?, ?);""",
                                            [obj.handle, 
                                             obj.__class__.__name__,
                                             ref_handle, 

From 800e8ebefb6c82231b13239a92fb318686bf91d3 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 23 May 2015 21:27:51 -0400
Subject: [PATCH 093/105] Added metadata table and setting/value

---
 gramps/plugins/database/dbapi.py | 48 ++++++++++++++++++++++++++------
 1 file changed, 39 insertions(+), 9 deletions(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index d501dbdd3..503194bf9 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -748,7 +748,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return gid
 
     def get_mediapath(self):
-        return None
+        cur = self.dbapi.execute("select * from metadata where setting = ?", ["media-path"])
+        row = cur.fetchone()
+        if row:
+            return row["value"]
+        else:
+            return None
 
     def get_name_group_keys(self):
         cur = self.dbapi.execute("select name from name_group order by name;")
@@ -1125,12 +1130,27 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.dbapi.commit()
 
     def set_default_person_handle(self, handle):
-        ## FIXME
-        pass
+        cur = self.dbapi.execute("select * from metadata where setting = ?", ["default-person"])
+        row = cur.fetchone()
+        if row:
+            cur = self.dbapi.execute("UPDATE metadata SET value = ? WHERE setting = ?;", 
+                                     [handle, "default-person"])
+        else:
+            cur = self.dbapi.execute("INSERT INTO metadata (setting, value) VALUES (?, ?);", 
+                                     ["default-person", handle])
+        self.emit('home-person-changed')
+        self.dbapi.commit()
 
     def set_mediapath(self, mediapath):
-        ## FIXME
-        pass
+        cur = self.dbapi.execute("select * from metadata where setting = ?", ["media-path"])
+        row = cur.fetchone()
+        if row:
+            cur = self.dbapi.execute("UPDATE metadata SET value = ? WHERE setting = ?;", 
+                                     [mediapath, "media-path"])
+        else:
+            cur = self.dbapi.execute("INSERT INTO metadata (setting, value) VALUES (?, ?);", 
+                                     ["media-path", mediapath])
+        self.dbapi.commit()
 
     def get_raw_person_data(self, handle):
         if handle in self.person_map:
@@ -1615,6 +1635,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             #del self.person_id_map[person.gramps_id]
             self.dbapi.execute("DELETE from person WHERE handle = ?;", [handle])
             self.emit("person-delete", ([handle],))
+            if not transaction.batch:
+                self.dbapi.commit()
 
     def remove_source(self, handle, transaction):
         """
@@ -1715,6 +1737,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.dbapi.execute("DELETE from %s WHERE handle = ?;" % key2table[key], 
                                [handle])
             self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],))
+            if not transaction.batch:
+                self.dbapi.commit()
 
     ## Missing:
 
@@ -1778,10 +1802,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return self.citation_bookmarks
 
     def get_default_handle(self):
-        items = self.person_map.keys()
-        if len(items) > 0:
-            return list(items)[0]
-        return None
+        cur = self.dbapi.execute("select * from metadata where setting = ?", ["default-person"])
+        row = cur.fetchone()
+        if row:
+            return row["value"]
+        else:
+            return None
 
     def get_event_attribute_types(self):
         ## FIXME
@@ -2017,6 +2043,10 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                     name     TEXT PRIMARY KEY NOT NULL,
                                     grouping TEXT
         );""")
+        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS metadata (
+                                    setting  TEXT PRIMARY KEY NOT NULL,
+                                    value    TEXT
+        );""")
         ## Indices:
         self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
                                   order_by ON person (order_by);

From dad21d1e2f1dbab9a29a6c7779341812313ac63a Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 23 May 2015 22:46:05 -0400
Subject: [PATCH 094/105] Update backlinks

---
 gramps/plugins/database/dbapi.py | 55 +++++++++++++++++++++++++-------
 1 file changed, 44 insertions(+), 11 deletions(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index 503194bf9..d4a2bf09e 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -1287,7 +1287,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return obj.handle
 
     def commit_person(self, person, trans, change_time=None):
-        ## FIXME: update reference, for back references
         emit = None
         if person.handle in self.person_map:
             emit = "person-update"
@@ -1309,12 +1308,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 pickle.dumps(person.serialize())])
         if not trans.batch:
             self.dbapi.commit()
+            self.update_backlinks(person)
         # Emit after added:
         if emit:
             self.emit(emit, ([person.handle],))
 
     def commit_family(self, family, trans, change_time=None):
-        ## FIXME: update reference, for back references
         emit = None
         if family.handle in self.family_map:
             emit = "family-update"
@@ -1332,12 +1331,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 pickle.dumps(family.serialize())])
         if not trans.batch:
             self.dbapi.commit()
+            self.update_backlinks(family)
         # Emit after added:
         if emit:
             self.emit(emit, ([family.handle],))
 
     def commit_citation(self, citation, trans, change_time=None):
-        ## FIXME: update reference, for back references
         emit = None
         if citation.handle in self.citation_map:
             emit = "citation-update"
@@ -1359,12 +1358,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                         pickle.dumps(citation.serialize())])
         if not trans.batch:
             self.dbapi.commit()
+            self.update_backlinks(citation)
         # Emit after added:
         if emit:
             self.emit(emit, ([citation.handle],))
 
     def commit_source(self, source, trans, change_time=None):
-        ## FIXME: update reference, for back references
         emit = None
         if source.handle in self.source_map:
             emit = "source-update"
@@ -1386,12 +1385,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                         pickle.dumps(source.serialize())])
         if not trans.batch:
             self.dbapi.commit()
+            self.update_backlinks(source)
         # Emit after added:
         if emit:
             self.emit(emit, ([source.handle],))
 
     def commit_repository(self, repository, trans, change_time=None):
-        ## FIXME: update reference, for back references
         emit = None
         if repository.handle in self.repository_map:
             emit = "repository-update"
@@ -1408,12 +1407,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                        [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())])
         if not trans.batch:
             self.dbapi.commit()
+            self.update_backlinks(repository)
         # Emit after added:
         if emit:
             self.emit(emit, ([repository.handle],))
 
     def commit_note(self, note, trans, change_time=None):
-        ## FIXME: update reference, for back references
         emit = None
         if note.handle in self.note_map:
             emit = "note-update"
@@ -1430,12 +1429,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                        [note.handle, note.gramps_id, pickle.dumps(note.serialize())])
         if not trans.batch:
             self.dbapi.commit()
+            self.update_backlinks(note)
         # Emit after added:
         if emit:
             self.emit(emit, ([note.handle],))
 
     def commit_place(self, place, trans, change_time=None):
-        ## FIXME: update reference, for back references
         emit = None
         if place.handle in self.place_map:
             emit = "place-update"
@@ -1457,12 +1456,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                         pickle.dumps(place.serialize())])
         if not trans.batch:
             self.dbapi.commit()
+            self.update_backlinks(place)
         # Emit after added:
         if emit:
             self.emit(emit, ([place.handle],))
 
     def commit_event(self, event, trans, change_time=None):
-        ## FIXME: update reference, for back references
         emit = None
         if event.handle in self.event_map:
             emit = "event-update"
@@ -1479,14 +1478,47 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                        [event.handle, 
                         event.gramps_id, 
                         pickle.dumps(event.serialize())])
+
+        self.update_event_attributes(
+            [str(attr.type) for attr in event.attribute_list
+             if attr.type.is_custom() and str(attr.type)])
+
+        if event.type.is_custom():
+            self.update_event_names(str(event.type))
+
         if not trans.batch:
             self.dbapi.commit()
+            self.update_backlinks(event)
+
         # Emit after added:
         if emit:
             self.emit(emit, ([event.handle],))
 
+    def update_backlinks(self, obj):
+        # First, delete the current references:
+        self.dbapi.execute("DELETE FROM reference where obj_handle = ?;",
+                           [obj.handle])
+        # Now, add the current ones:
+        references = set(obj.get_referenced_handles_recursively())
+        for (ref_class_name, ref_handle) in references:
+            self.dbapi.execute("""INSERT into reference 
+                       (obj_handle, obj_class, ref_handle, ref_class)
+                       VALUES(?, ?, ?, ?);""",
+                               [obj.handle, 
+                                obj.__class__.__name__,
+                                ref_handle, 
+                                ref_class_name])
+        # Will commit later
+
+    def update_event_attributes(self, attr_list):
+        # FIXME
+        pass
+
+    def update_event_names(self, event_type):
+        # FIXME
+        pass
+
     def commit_tag(self, tag, trans, change_time=None):
-        ## FIXME: update reference, for back references
         emit = None
         if tag.handle in self.tag_map:
             emit = "tag-update"
@@ -1505,12 +1537,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                         pickle.dumps(tag.serialize())])
         if not trans.batch:
             self.dbapi.commit()
+            self.update_backlinks(tag)
         # Emit after added:
         if emit:
             self.emit(emit, ([tag.handle],))
 
     def commit_media_object(self, media, trans, change_time=None):
-        ## FIXME: update reference, for back references
         emit = None
         if media.handle in self.media_map:
             emit = "media-update"
@@ -1532,6 +1564,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                         pickle.dumps(media.serialize())])
         if not trans.batch:
             self.dbapi.commit()
+            self.update_backlinks(media)
         # Emit after added:
         if emit:
             self.emit(emit, ([media.handle],))
@@ -2197,7 +2230,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         Commits the items that were queued up during the last gedcom
         import for two step adding.
         """
-        pass
+        self.reindex_reference_map(lambda n: n)
 
     def has_handle_for_person(self, key):
         cur = self.dbapi.execute("select * from person where handle = ?", [key])

From d5c9c5114a8cfdaa9c95f89dccbec59fa073b9b8 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Mon, 25 May 2015 13:33:04 -0400
Subject: [PATCH 095/105] All metadata functionality now implemented

---
 .../plugins/database/bsddb_support/write.py   |   2 +-
 gramps/plugins/database/dbapi.py              | 491 ++++++++++++------
 gramps/plugins/database/dictionarydb.py       |   2 +-
 gramps/plugins/database/djangodb.py           |   2 +-
 4 files changed, 327 insertions(+), 170 deletions(-)

diff --git a/gramps/plugins/database/bsddb_support/write.py b/gramps/plugins/database/bsddb_support/write.py
index 65bf71980..2eb665ac5 100644
--- a/gramps/plugins/database/bsddb_support/write.py
+++ b/gramps/plugins/database/bsddb_support/write.py
@@ -653,7 +653,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
         return False
 
     @catch_db_error
-    def load(self, name, callback, mode=DBMODE_W, force_schema_upgrade=False,
+    def load(self, name, callback=None, mode=DBMODE_W, force_schema_upgrade=False,
              force_bsddb_upgrade=False, force_bsddb_downgrade=False,
              force_python_upgrade=False):
 
diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index d4a2bf09e..aad21039e 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -167,13 +167,34 @@ class Cursor(object):
         pass
 
 class Bookmarks(object):
-    def __init__(self):
-        self.handles = []
+    def __init__(self, default=[]):
+        self.handles = list(default)
+
+    def set(self, handles):
+        self.handles = list(handles)
+
     def get(self):
         return self.handles
+
     def append(self, handle):
         self.handles.append(handle)
 
+    def append_list(self, handles):
+        self.handles += handles
+
+    def remove(self, handle):
+        self.handles.remove(handle)
+
+    def pop(self, item):
+        return self.handles.pop(item)
+
+    def insert(self, pos, item):
+        self.handles.insert(pos, item)
+
+    def close(self):
+        del self.handles
+    
+
 class DBAPITxn(DbTxn):
     def __init__(self, message, db, batch=False):
         DbTxn.__init__(self, message, db, batch)
@@ -386,6 +407,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.readonly = False
         self.db_is_open = True
         self.name_formats = []
+        # Bookmarks:
         self.bookmarks = Bookmarks()
         self.family_bookmarks = Bookmarks()
         self.event_bookmarks = Bookmarks()
@@ -443,7 +465,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.source_map = Map(Table(self._tables["Source"]))
         self.source_id_map = Map(Table(self._tables["Source"]),
                                  keys_func="ids_func",
-                                 contains_func="has_gramps_id_func")
+                                contains_func="has_gramps_id_func")
         self.repository_map  = Map(Table(self._tables["Repository"]))
         self.repository_id_map = Map(Table(self._tables["Repository"]),
                                  keys_func="ids_func",
@@ -478,6 +500,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.abort_possible = False
         self._bm_changes = 0
         self._directory = directory
+        self._has_changed = False
         self.full_name = None
         self.path = None
         self.brief_name = None
@@ -503,10 +526,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def transaction_commit(self, txn):
         self.dbapi.commit()
 
-    def get_undodb(self):
-        ## FIXME
-        return None
-
     def transaction_abort(self, txn):
         self.dbapi.rollback()
 
@@ -748,12 +767,42 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return gid
 
     def get_mediapath(self):
-        cur = self.dbapi.execute("select * from metadata where setting = ?", ["media-path"])
+        return self.get_metadata("media-path", "")
+
+    def set_mediapath(self, mediapath):
+        return self.set_metadata("media-path", mediapath)
+
+    def get_metadata(self, key, default=[]):
+        """
+        Get an item from the database.
+        """
+        cur = self.dbapi.execute("SELECT * FROM metadata WHERE setting = ?;", [key])
         row = cur.fetchone()
         if row:
-            return row["value"]
+            return pickle.loads(row["value"])
+        elif default == []:
+            return []
         else:
-            return None
+            return default
+
+    def set_metadata(self, key, value):
+        """
+        key: string
+        value: item, will be serialized here
+        """
+        cur = self.dbapi.execute("SELECT * FROM metadata WHERE setting = ?;", [key])
+        row = cur.fetchone()
+        if row:
+            cur = self.dbapi.execute("UPDATE metadata SET value = ? WHERE setting = ?;", 
+                                     [pickle.dumps(value), key])
+        else:
+            cur = self.dbapi.execute("INSERT INTO metadata (setting, value) VALUES (?, ?);", 
+                                     [key, pickle.dumps(value)])
+        self.dbapi.commit()
+
+    def set_default_person_handle(self, handle):
+        self.set_metadata("default-person-handle", handle)
+        self.emit('home-person-changed')
 
     def get_name_group_keys(self):
         cur = self.dbapi.execute("select name from name_group order by name;")
@@ -1129,29 +1178,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                  [sname, grouping])
         self.dbapi.commit()
 
-    def set_default_person_handle(self, handle):
-        cur = self.dbapi.execute("select * from metadata where setting = ?", ["default-person"])
-        row = cur.fetchone()
-        if row:
-            cur = self.dbapi.execute("UPDATE metadata SET value = ? WHERE setting = ?;", 
-                                     [handle, "default-person"])
-        else:
-            cur = self.dbapi.execute("INSERT INTO metadata (setting, value) VALUES (?, ?);", 
-                                     ["default-person", handle])
-        self.emit('home-person-changed')
-        self.dbapi.commit()
-
-    def set_mediapath(self, mediapath):
-        cur = self.dbapi.execute("select * from metadata where setting = ?", ["media-path"])
-        row = cur.fetchone()
-        if row:
-            cur = self.dbapi.execute("UPDATE metadata SET value = ? WHERE setting = ?;", 
-                                     [mediapath, "media-path"])
-        else:
-            cur = self.dbapi.execute("INSERT INTO metadata (setting, value) VALUES (?, ?);", 
-                                     ["media-path", mediapath])
-        self.dbapi.commit()
-
     def get_raw_person_data(self, handle):
         if handle in self.person_map:
             return self.person_map[handle]
@@ -1312,6 +1338,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # Emit after added:
         if emit:
             self.emit(emit, ([person.handle],))
+        self._has_changed = True
 
     def commit_family(self, family, trans, change_time=None):
         emit = None
@@ -1335,6 +1362,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # Emit after added:
         if emit:
             self.emit(emit, ([family.handle],))
+        self._has_changed = True
 
     def commit_citation(self, citation, trans, change_time=None):
         emit = None
@@ -1362,6 +1390,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # Emit after added:
         if emit:
             self.emit(emit, ([citation.handle],))
+        self._has_changed = True
 
     def commit_source(self, source, trans, change_time=None):
         emit = None
@@ -1389,6 +1418,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # Emit after added:
         if emit:
             self.emit(emit, ([source.handle],))
+        self._has_changed = True
 
     def commit_repository(self, repository, trans, change_time=None):
         emit = None
@@ -1411,6 +1441,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # Emit after added:
         if emit:
             self.emit(emit, ([repository.handle],))
+        self._has_changed = True
 
     def commit_note(self, note, trans, change_time=None):
         emit = None
@@ -1433,6 +1464,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # Emit after added:
         if emit:
             self.emit(emit, ([note.handle],))
+        self._has_changed = True
 
     def commit_place(self, place, trans, change_time=None):
         emit = None
@@ -1460,6 +1492,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # Emit after added:
         if emit:
             self.emit(emit, ([place.handle],))
+        self._has_changed = True
 
     def commit_event(self, event, trans, change_time=None):
         emit = None
@@ -1493,6 +1526,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # Emit after added:
         if emit:
             self.emit(emit, ([event.handle],))
+        self._has_changed = True
 
     def update_backlinks(self, obj):
         # First, delete the current references:
@@ -1773,12 +1807,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             if not transaction.batch:
                 self.dbapi.commit()
 
-    ## Missing:
-
-    def backup(self):
-        ## FIXME
-        pass
-
     def close(self):
         if self._directory:
             from gramps.plugins.export.exportxml import XmlWriter
@@ -1788,6 +1816,39 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             writer.write(filename)
             filename = os.path.join(self._directory, "meta_data.db")
             touch(filename)
+            # Save metadata
+            self.set_metadata('bookmarks', self.bookmarks.get())
+            self.set_metadata('family_bookmarks', self.family_bookmarks.get())
+            self.set_metadata('event_bookmarks', self.event_bookmarks.get())
+            self.set_metadata('source_bookmarks', self.source_bookmarks.get())
+            self.set_metadata('citation_bookmarks', self.citation_bookmarks.get())
+            self.set_metadata('repo_bookmarks', self.repo_bookmarks.get())
+            self.set_metadata('media_bookmarks', self.media_bookmarks.get())
+            self.set_metadata('place_bookmarks', self.place_bookmarks.get())
+            self.set_metadata('note_bookmarks', self.note_bookmarks.get())
+            
+            # Custom type values, sets
+            self.set_metadata('event_names', self.event_names)
+            self.set_metadata('fattr_names', self.family_attributes)
+            self.set_metadata('pattr_names', self.individual_attributes)
+            self.set_metadata('sattr_names', self.source_attributes)
+            self.set_metadata('marker_names', self.marker_names)
+            self.set_metadata('child_refs', self.child_ref_types)
+            self.set_metadata('family_rels', self.family_rel_types)
+            self.set_metadata('event_roles', self.event_role_names)
+            self.set_metadata('name_types', self.name_types)
+            self.set_metadata('origin_types', self.origin_types)
+            self.set_metadata('repo_types', self.repository_types)
+            self.set_metadata('note_types', self.note_types)
+            self.set_metadata('sm_types', self.source_media_types)
+            self.set_metadata('url_types', self.url_types)
+            self.set_metadata('mattr_names', self.media_attributes)
+            self.set_metadata('eattr_names', self.event_attributes)
+            self.set_metadata('place_types', self.place_types)
+            
+            # surname list
+            self.set_metadata('surname_list', self.surname_list)
+            
             self.dbapi.close()
 
     def find_backlink_handles(self, handle, include_classes=None):
@@ -1815,136 +1876,185 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 yield (row["obj_class"], row["obj_handle"])
 
     def find_initial_person(self):
-        items = self.person_map.keys()
-        if len(items) > 0:
-            return self.get_person_from_handle(list(items)[0])
-        return None
-
-    def find_place_child_handles(self, handle):
-        ## FIXME
-        return []
+        handle = self.get_default_handle()
+        person = None
+        if handle:
+            person = self.get_person_from_handle(handle)
+            if person:
+                return person
+        cur = self.dbapi.execute("SELECT handle FROM person;")
+        row = cur.fetchone()
+        if row:
+            return row[0]
 
     def get_bookmarks(self):
         return self.bookmarks
 
-    def get_child_reference_types(self):
-        ## FIXME
-        return []
-
     def get_citation_bookmarks(self):
         return self.citation_bookmarks
 
     def get_default_handle(self):
-        cur = self.dbapi.execute("select * from metadata where setting = ?", ["default-person"])
-        row = cur.fetchone()
-        if row:
-            return row["value"]
-        else:
-            return None
+        return self.get_metadata("default-person-handle", None)
 
-    def get_event_attribute_types(self):
-        ## FIXME
-        return []
-
-    def get_event_bookmarks(self):
-        return self.event_bookmarks
-
-    def get_event_roles(self):
-        ## FIXME
-        return []
-
-    def get_event_types(self):
-        ## FIXME
-        return []
-
-    def get_family_attribute_types(self):
-        ## FIXME
-        return []
-
-    def get_family_bookmarks(self):
-        return self.family_bookmarks
-
-    def get_family_event_types(self):
-        ## FIXME
-        return []
-
-    def get_family_relation_types(self):
-        ## FIXME
-        return []
-
-    def get_media_attribute_types(self):
-        ## FIXME
-        return []
-
-    def get_media_bookmarks(self):
-        return self.media_bookmarks
-
-    def get_name_types(self):
-        ## FIXME
-        return []
-
-    def get_note_bookmarks(self):
-        return self.note_bookmarks
-
-    def get_note_types(self):
-        ## FIXME
-        return []
-
-    def get_origin_types(self):
-        ## FIXME
-        return []
-
-    def get_person_attribute_types(self):
-        ## FIXME
-        return []
-
-    def get_person_event_types(self):
-        ## FIXME
-        return []
-
-    def get_place_bookmarks(self):
-        return self.place_bookmarks
-
-    def get_place_tree_cursor(self):
-        ## FIXME
-        return []
-
-    def get_place_types(self):
-        ## FIXME
-        return []
-
-    def get_repo_bookmarks(self):
-        return self.repo_bookmarks
-
-    def get_repository_types(self):
-        ## FIXME
-        return []
-
-    def get_save_path(self):
-        return self._directory
-
-    def get_source_attribute_types(self):
-        ## FIXME
-        return []
-
-    def get_source_bookmarks(self):
-        return self.source_bookmarks
-
-    def get_source_media_types(self):
+    def find_place_child_handles(self, handle):
         ## FIXME
         return []
 
     def get_surname_list(self):
-        ## FIXME
-        return []
+        """
+        Return the list of locale-sorted surnames contained in the database.
+        """
+        return self.surname_list
+
+    def get_event_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Event instances
+        in the database.
+        """
+        return list(self.event_attributes)
+
+    def get_event_types(self):
+        """
+        Return a list of all event types in the database.
+        """
+        return list(self.event_names)
+
+    def get_person_event_types(self):
+        """
+        Deprecated:  Use get_event_types
+        """
+        return list(self.event_names)
+
+    def get_person_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Person instances 
+        in the database.
+        """
+        return list(self.individual_attributes)
+
+    def get_family_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Family instances 
+        in the database.
+        """
+        return list(self.family_attributes)
+
+    def get_family_event_types(self):
+        """
+        Deprecated:  Use get_event_types
+        """
+        return list(self.event_names)
+
+    def get_media_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Media and MediaRef 
+        instances in the database.
+        """
+        return list(self.media_attributes)
+
+    def get_family_relation_types(self):
+        """
+        Return a list of all relationship types assocated with Family
+        instances in the database.
+        """
+        return list(self.family_rel_types)
+
+    def get_child_reference_types(self):
+        """
+        Return a list of all child reference types assocated with Family
+        instances in the database.
+        """
+        return list(self.child_ref_types)
+
+    def get_event_roles(self):
+        """
+        Return a list of all custom event role names assocated with Event
+        instances in the database.
+        """
+        return list(self.event_role_names)
+
+    def get_name_types(self):
+        """
+        Return a list of all custom names types assocated with Person
+        instances in the database.
+        """
+        return list(self.name_types)
+
+    def get_origin_types(self):
+        """
+        Return a list of all custom origin types assocated with Person/Surname
+        instances in the database.
+        """
+        return list(self.origin_types)
+
+    def get_repository_types(self):
+        """
+        Return a list of all custom repository types assocated with Repository 
+        instances in the database.
+        """
+        return list(self.repository_types)
+
+    def get_note_types(self):
+        """
+        Return a list of all custom note types assocated with Note instances 
+        in the database.
+        """
+        return list(self.note_types)
+
+    def get_source_attribute_types(self):
+        """
+        Return a list of all Attribute types assocated with Source/Citation
+        instances in the database.
+        """
+        return list(self.source_attributes)
+
+    def get_source_media_types(self):
+        """
+        Return a list of all custom source media types assocated with Source 
+        instances in the database.
+        """
+        return list(self.source_media_types)
 
     def get_url_types(self):
-        ## FIXME
-        return []
+        """
+        Return a list of all custom names types assocated with Url instances 
+        in the database.
+        """
+        return list(self.url_types)
+
+    def get_place_types(self):
+        """
+        Return a list of all custom place types assocated with Place instances
+        in the database.
+        """
+        return list(self.place_types)
+
+    def get_event_bookmarks(self):
+        return self.event_bookmarks
+
+    def get_family_bookmarks(self):
+        return self.family_bookmarks
+
+    def get_media_bookmarks(self):
+        return self.media_bookmarks
+
+    def get_note_bookmarks(self):
+        return self.note_bookmarks
+
+    def get_place_bookmarks(self):
+        return self.place_bookmarks
+
+    def get_repo_bookmarks(self):
+        return self.repo_bookmarks
+
+    def get_save_path(self):
+        return self._directory
+
+    def get_source_bookmarks(self):
+        return self.source_bookmarks
 
     def has_changed(self):
-        ## FIXME
-        return True
+        return self._has_changed
 
     def is_open(self):
         return self._directory is not None
@@ -1994,7 +2104,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def iter_tags(self):
         return (Tag.create(data[1]) for data in self.get_tag_cursor())
 
-    def load(self, directory, pulse_progress=None, mode=None, 
+    def load(self, directory, callback=None, mode=None, 
              force_schema_upgrade=False, 
              force_bsddb_upgrade=False, 
              force_bsddb_downgrade=False, 
@@ -2102,19 +2212,50 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
                                   ref_handle ON reference (ref_handle);
         """)
+        # Load metadata
+        self.bookmarks.set(self.get_metadata('bookmarks'))
+        self.family_bookmarks.set(self.get_metadata('family_bookmarks'))
+        self.event_bookmarks.set(self.get_metadata('event_bookmarks'))
+        self.source_bookmarks.set(self.get_metadata('source_bookmarks'))
+        self.citation_bookmarks.set(self.get_metadata('citation_bookmarks'))
+        self.repo_bookmarks.set(self.get_metadata('repo_bookmarks'))
+        self.media_bookmarks.set(self.get_metadata('media_bookmarks'))
+        self.place_bookmarks.set(self.get_metadata('place_bookmarks'))
+        self.note_bookmarks.set(self.get_metadata('note_bookmarks'))
 
-    def redo(self, update_history=True):
-        ## FIXME
-        pass
-
-    def restore(self):
-        ## FIXME
-        pass
-
+        # Custom type values
+        self.event_names = self.get_metadata('event_names', set())
+        self.family_attributes = self.get_metadata('fattr_names', set())
+        self.individual_attributes = self.get_metadata('pattr_names', set())
+        self.source_attributes = self.get_metadata('sattr_names', set())
+        self.marker_names = self.get_metadata('marker_names', set())
+        self.child_ref_types = self.get_metadata('child_refs', set())
+        self.family_rel_types = self.get_metadata('family_rels', set())
+        self.event_role_names = self.get_metadata('event_roles', set())
+        self.name_types = self.get_metadata('name_types', set())
+        self.origin_types = self.get_metadata('origin_types', set())
+        self.repository_types = self.get_metadata('repo_types', set())
+        self.note_types = self.get_metadata('note_types', set())
+        self.source_media_types = self.get_metadata('sm_types', set())
+        self.url_types = self.get_metadata('url_types', set())
+        self.media_attributes = self.get_metadata('mattr_names', set())
+        self.event_attributes = self.get_metadata('eattr_names', set())
+        self.place_types = self.get_metadata('place_types', set())
+        
+        # surname list
+        self.surname_list = self.get_metadata('surname_list')
+        
     def set_prefixes(self, person, media, family, source, citation, 
                      place, event, repository, note):
-        ## FIXME
-        pass
+        self.set_person_id_prefix(person)
+        self.set_object_id_prefix(media)
+        self.set_family_id_prefix(family)
+        self.set_source_id_prefix(source)
+        self.set_citation_id_prefix(citation)
+        self.set_place_id_prefix(place)
+        self.set_event_id_prefix(event)
+        self.set_repository_id_prefix(repository)
+        self.set_note_id_prefix(note)
 
     def set_save_path(self, directory):
         self._directory = directory
@@ -2122,10 +2263,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.path = self.full_name
         self.brief_name = os.path.basename(self._directory)
 
-    def undo(self, update_history=True):
-        ## FIXME
-        pass
-
     def write_version(self, directory):
         """Write files for a newly created DB."""
         versionpath = os.path.join(directory, str(DBBACKEND))
@@ -2438,3 +2575,23 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def _order_by_tag_key(self, tag):
         return glocale.sort_key(tag.get_name())
 
+    def backup(self):
+        ## FIXME
+        pass
+
+    def restore(self):
+        ## FIXME
+        pass
+
+    def get_undodb(self):
+        ## FIXME
+        return None
+
+    def undo(self, update_history=True):
+        ## FIXME
+        pass
+
+    def redo(self, update_history=True):
+        ## FIXME
+        pass
+
diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py
index 8b8d8beb7..3fe7e0da5 100644
--- a/gramps/plugins/database/dictionarydb.py
+++ b/gramps/plugins/database/dictionarydb.py
@@ -1725,7 +1725,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def iter_tags(self):
         return (Tag.create(key) for key in self.tag_map.values())
 
-    def load(self, directory, pulse_progress=None, mode=None, 
+    def load(self, directory, callback=None, mode=None, 
              force_schema_upgrade=False, 
              force_bsddb_upgrade=False, 
              force_bsddb_downgrade=False, 
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
index 0091d033a..a7f64f13e 100644
--- a/gramps/plugins/database/djangodb.py
+++ b/gramps/plugins/database/djangodb.py
@@ -360,7 +360,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if directory:
             self.load(directory)
 
-    def load(self, directory, pulse_progress=None, mode=None, 
+    def load(self, directory, callback=None, mode=None, 
              force_schema_upgrade=False,
              force_bsddb_upgrade=False,
              force_bsddb_downgrade=False,

From 1b8923932386d9bf4c3d031f626c45591ebbdb1a Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Mon, 25 May 2015 17:53:14 -0400
Subject: [PATCH 096/105] DB-API: added undo-redo infrastructure

---
 gramps/gen/db/__init__.py        |   1 +
 gramps/gen/db/undoredo.py        |  73 +++++++++++++++++-----
 gramps/plugins/database/dbapi.py | 101 +++++++++++++++++++++++++------
 3 files changed, 141 insertions(+), 34 deletions(-)

diff --git a/gramps/gen/db/__init__.py b/gramps/gen/db/__init__.py
index 10f751303..d9fcf1ad1 100644
--- a/gramps/gen/db/__init__.py
+++ b/gramps/gen/db/__init__.py
@@ -88,6 +88,7 @@ from .base import *
 from .dbconst import *
 from .txn import *
 from .exceptions import *
+from .undoredo import *
 
 def find_surname_name(key, data):
     """
diff --git a/gramps/gen/db/undoredo.py b/gramps/gen/db/undoredo.py
index b12735093..bfcd652d6 100644
--- a/gramps/gen/db/undoredo.py
+++ b/gramps/gen/db/undoredo.py
@@ -1,4 +1,10 @@
+#-------------------------------------------------------------------------
+#
+# Standard python modules
+#
+#-------------------------------------------------------------------------
 import time
+import pickle
 from collections import deque
 
 class DbUndo(object):
@@ -19,6 +25,20 @@ class DbUndo(object):
         self.redoq = deque()
         self.undo_history_timestamp = time.time()
         self.txn = None
+        # N.B. the databases have to be in the same order as the numbers in
+        # xxx_KEY in gen/db/dbconst.py
+        self.mapbase = (
+                        self.db.person_map,
+                        self.db.family_map,
+                        self.db.source_map,
+                        self.db.event_map,
+                        self.db.media_map,
+                        self.db.place_map,
+                        self.db.repository_map,
+                        self.db.note_map,
+                        self.db.tag_map,
+                        self.db.citation_map,
+                        )
 
     def clear(self):
         """
@@ -85,6 +105,16 @@ class DbUndo(object):
         """         
         raise NotImplementedError
 
+    def __redo(self, update_history):
+        """
+        """
+        raise NotImplementedError
+
+    def __undo(self, update_history):
+        """
+        """
+        raise NotImplementedError
+
     def commit(self, txn, msg):
         """
         Commit the transaction to the undo/redo database.  "txn" should be
@@ -110,27 +140,40 @@ class DbUndo(object):
             return False
         return self.__redo(update_history)
 
-    def undoredo(func):
+    def undo_reference(self, data, handle, db_map):
         """
-        Decorator function to wrap undo and redo operations within a bsddb
-        transaction.  It also catches bsddb errors and raises an exception
-        as appropriate
+        Helper method to undo a reference map entry
         """
-        pass
+        try:
+            if data is None:
+                db_map.delete(handle, txn=self.txn)
+            else:
+                db_map.put(handle, data, txn=self.txn)
 
-    def __redo(self, update_history=True):
-        """
-        Access the last undone transaction, and revert the data to the state 
-        before the transaction was undone.
-        """
-        pass
+        except DBERRS as msg:
+            self.db._log_error()
+            raise DbError(msg)
 
-    def __undo(self, db=None, update_history=True):
+    def undo_data(self, data, handle, db_map, emit, signal_root):
         """
-        Access the last committed transaction, and revert the data to the 
-        state before the transaction was committed.
+        Helper method to undo/redo the changes made
         """
-        pass
+        try:
+            if data is None:
+                emit(signal_root + '-delete', ([handle2internal(handle)],))
+                db_map.delete(handle, txn=self.txn)
+            else:
+                ex_data = db_map.get(handle, txn=self.txn)
+                if ex_data:
+                    signal = signal_root + '-update'
+                else:
+                    signal = signal_root + '-add'
+                db_map.put(handle, data, txn=self.txn)
+                emit(signal, ([handle2internal(handle)],))
+
+        except DBERRS as msg:
+            self.db._log_error()
+            raise DbError(msg)
 
     undo_count = property(lambda self:len(self.undoq))
     redo_count = property(lambda self:len(self.redoq))
diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index aad21039e..9df140e32 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -19,12 +19,11 @@ import shutil
 import gramps
 from gramps.gen.const import GRAMPS_LOCALE as glocale
 _ = glocale.translation.gettext
-from gramps.gen.db import (DbReadBase, DbWriteBase, DbTxn, 
+from gramps.gen.db import (DbReadBase, DbWriteBase, DbTxn, DbUndo,
                            KEY_TO_NAME_MAP, KEY_TO_CLASS_MAP, 
                            CLASS_TO_KEY_MAP)
 from gramps.gen.utils.callback import Callback
 from gramps.gen.updatecallback import UpdateCallback
-from gramps.gen.db.undoredo import DbUndo
 from gramps.gen.db.dbconst import *
 from gramps.gen.db import (PERSON_KEY,
                            FAMILY_KEY,
@@ -52,6 +51,71 @@ def touch(fname, mode=0o666, dir_fd=None, **kwargs):
         os.utime(f.fileno() if os.utime in os.supports_fd else fname,
                  dir_fd=None if os.supports_fd else dir_fd, **kwargs)
 
+class DBAPIUndo(DbUndo):
+    def __init__(self, grampsdb, path):
+        super(DBAPIUndo, self).__init__(grampsdb)
+        self.undodb = grampsdb
+        self.path = path
+
+    def open(self, value=None):
+        """
+        Open the backing storage.  Needs to be overridden in the derived
+        class.
+        """
+        pass
+        # FIXME
+
+    def close(self):
+        """
+        Close the backing storage.  Needs to be overridden in the derived
+        class.
+        """        
+        pass
+        # FIXME
+
+    def append(self, value):
+        """
+        Add a new entry on the end.  Needs to be overridden in the derived
+        class.
+        """        
+        pass
+        # FIXME
+
+    def __getitem__(self, index):
+        """
+        Returns an entry by index number.  Needs to be overridden in the
+        derived class.
+        """        
+        return None
+        # FIXME
+
+    def __setitem__(self, index, value):
+        """
+        Set an entry to a value.  Needs to be overridden in the derived class.
+        """           
+        pass
+        # FIXME
+
+    def __len__(self):
+        """
+        Returns the number of entries.  Needs to be overridden in the derived
+        class.
+        """         
+        return 0
+        # FIXME
+
+    def __redo(self, update_history):
+        """
+        """
+        pass
+        # FIXME
+
+    def __undo(self, update_history):
+        """
+        """
+        pass
+        # FIXME
+
 class Environment(object):
     """
     Implements the Environment API.
@@ -427,6 +491,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.set_repository_id_prefix('R%04d')
         self.set_note_id_prefix('N%04d')
         # ----------------------------------
+        self.undodb = None
         self.id_trans  = DBAPITxn("ID Transaction", self)
         self.fid_trans = DBAPITxn("FID Transaction", self)
         self.pid_trans = DBAPITxn("PID Transaction", self)
@@ -484,19 +549,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                  contains_func="has_gramps_id_func")
         self.tag_map  = Map(Table(self._tables["Tag"]))
         self.metadata   = Map(Table({"cursor_func": lambda: MetaCursor()}))
-        ## FIXME: add appropriate methods:
-        self.name_group = Map(Table({"handles_func": self.get_name_group_keys,  # keys
-                                     "has_handle_func": self.has_name_group_key, # key in table 
-                                     "cursor_func": None, # create a cursor, values
-                                     "add_func": self.set_name_group_mapping, # add a key, value
-                                     "count_func": None})) # how many items?
         self.undo_callback = None
         self.redo_callback = None
         self.undo_history_callback = None
         self.modified   = 0
         self.txn = DBAPITxn("DBAPI Transaction", self)
         self.transaction = None
-        self.undodb = DbUndo(self)
         self.abort_possible = False
         self._bm_changes = 0
         self._directory = directory
@@ -984,7 +1042,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return (handle for handle in self.family_map.keys())
 
     def get_tag_from_name(self, name):
-        ## Slow, but typically not too many tags:
+        ## FIXME: Slow, but typically not too many tags:
         for data in self.tag_map.values():
             tag = Tag.create(data)
             if tag.name == name:
@@ -2244,6 +2302,11 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         
         # surname list
         self.surname_list = self.get_metadata('surname_list')
+
+        self._directory = directory
+        self.undolog = os.path.join(self._directory, DBUNDOFN)
+        self.undodb = DBAPIUndo(self, self.undolog)
+        self.undodb.open()
         
     def set_prefixes(self, person, media, family, source, citation, 
                      place, event, repository, note):
@@ -2576,22 +2639,22 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return glocale.sort_key(tag.get_name())
 
     def backup(self):
-        ## FIXME
+        """
+        If you wish to support an optional backup routine, put it here.
+        """
         pass
 
     def restore(self):
-        ## FIXME
+        """
+        If you wish to support an optional restore routine, put it here.
+        """
         pass
 
     def get_undodb(self):
-        ## FIXME
-        return None
+        return self.undodb
 
     def undo(self, update_history=True):
-        ## FIXME
-        pass
+        return self.undodb.undo(update_history)
 
     def redo(self, update_history=True):
-        ## FIXME
-        pass
-
+        return self.undodb.redo(update_history)

From 1c4f827bf85c93a3c19f8e7c94be588db81b2336 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Mon, 25 May 2015 19:04:27 -0400
Subject: [PATCH 097/105] DB-API: committing objects updates secondary items

---
 gramps/plugins/database/dbapi.py | 173 ++++++++++++++++++++++++++++---
 1 file changed, 160 insertions(+), 13 deletions(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index 9df140e32..c8673104f 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -10,6 +10,7 @@ import re
 import os
 import logging
 import shutil
+import bisect
 
 #------------------------------------------------------------------------
 #
@@ -1374,6 +1375,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         emit = None
         if person.handle in self.person_map:
             emit = "person-update"
+            old_person = self.get_person_from_handle(person.handle)
+            # Update gender statistics if necessary
+            if (old_person.gender != person.gender or
+                old_person.primary_name.first_name !=
+                  person.primary_name.first_name):
+
+                self.genderStats.uncount_person(old_person)
+                self.genderStats.count_person(person)
+            # Update surname list if necessary
+            if (self._order_by_person_key(person) != 
+                self._order_by_person_key(old_person)):
+                self.remove_from_surname_list(old_person)
+                self.add_to_surname_list(person, trans.batch)
+            # update the person:
             self.dbapi.execute("""UPDATE person SET gramps_id = ?, 
                                                     order_by = ?,
                                                     blob = ? 
@@ -1384,6 +1399,9 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 person.handle])
         else:
             emit = "person-add"
+            self.genderStats.count_person(person)
+            self.add_to_surname_list(person, trans.batch)
+            # Insert the person:
             self.dbapi.execute("""insert into person (handle, order_by, gramps_id, blob)
                             values(?, ?, ?, ?);""", 
                                [person.handle, 
@@ -1393,11 +1411,68 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not trans.batch:
             self.dbapi.commit()
             self.update_backlinks(person)
+        # Other misc update tasks:
+        self.individual_attributes.update(
+            [str(attr.type) for attr in person.attribute_list
+             if attr.type.is_custom() and str(attr.type)])
+
+        self.event_role_names.update([str(eref.role)
+                                      for eref in person.event_ref_list
+                                      if eref.role.is_custom()])
+
+        self.name_types.update([str(name.type)
+                                for name in ([person.primary_name]
+                                             + person.alternate_names)
+                                if name.type.is_custom()])
+        all_surn = []  # new list we will use for storage
+        all_surn += person.primary_name.get_surname_list() 
+        for asurname in person.alternate_names:
+            all_surn += asurname.get_surname_list()
+        self.origin_types.update([str(surn.origintype) for surn in all_surn
+                                if surn.origintype.is_custom()])
+        all_surn = None
+        self.url_types.update([str(url.type) for url in person.urls
+                               if url.type.is_custom()])
+        attr_list = []
+        for mref in person.media_list:
+            attr_list += [str(attr.type) for attr in mref.attribute_list
+                          if attr.type.is_custom() and str(attr.type)]
+        self.media_attributes.update(attr_list)
         # Emit after added:
         if emit:
             self.emit(emit, ([person.handle],))
         self._has_changed = True
 
+    def add_to_surname_list(self, person, batch_transaction):
+        """
+        Add surname to surname list
+        """
+        if batch_transaction:
+            return
+        name = conv_to_unicode(self._order_by_person_key(person), 'utf-8')
+        i = bisect.bisect(self.surname_list, name)
+        if 0 < i <= len(self.surname_list):
+            if self.surname_list[i-1] != name:
+                self.surname_list.insert(i, name)
+        else:
+            self.surname_list.insert(i, name)
+
+    def remove_from_surname_list(self, person):
+        """
+        Check whether there are persons with the same surname left in
+        the database. 
+        
+        If not then we need to remove the name from the list.
+        The function must be overridden in the derived class.
+        """
+        name = self._order_by_person_key(person)
+        if isinstance(name, str):
+            uname = name
+            name = name.encode('utf-8')
+        else:
+            uname = str(name)
+        # FIXME: check database
+
     def commit_family(self, family, trans, change_time=None):
         emit = None
         if family.handle in self.family_map:
@@ -1417,6 +1492,31 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not trans.batch:
             self.dbapi.commit()
             self.update_backlinks(family)
+        # Misc updates:
+        self.family_attributes.update(
+            [str(attr.type) for attr in family.attribute_list
+             if attr.type.is_custom() and str(attr.type)])
+
+        rel_list = []
+        for ref in family.child_ref_list:
+            if ref.frel.is_custom():
+                rel_list.append(str(ref.frel))
+            if ref.mrel.is_custom():
+                rel_list.append(str(ref.mrel))
+        self.child_ref_types.update(rel_list)
+
+        self.event_role_names.update(
+            [str(eref.role) for eref in family.event_ref_list
+             if eref.role.is_custom()])
+
+        if family.type.is_custom():
+            self.family_rel_types.add(str(family.type))
+
+        attr_list = []
+        for mref in family.media_list:
+            attr_list += [str(attr.type) for attr in mref.attribute_list
+                          if attr.type.is_custom() and str(attr.type)]
+        self.media_attributes.update(attr_list)
         # Emit after added:
         if emit:
             self.emit(emit, ([family.handle],))
@@ -1445,6 +1545,17 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not trans.batch:
             self.dbapi.commit()
             self.update_backlinks(citation)
+        # Misc updates:
+        attr_list = []
+        for mref in citation.media_list:
+            attr_list += [str(attr.type) for attr in mref.attribute_list
+                          if attr.type.is_custom() and str(attr.type)]
+        self.media_attributes.update(attr_list)
+
+        self.source_attributes.update(
+            [str(attr.type) for attr in citation.attribute_list
+             if attr.type.is_custom() and str(attr.type)])
+
         # Emit after added:
         if emit:
             self.emit(emit, ([citation.handle],))
@@ -1473,6 +1584,19 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not trans.batch:
             self.dbapi.commit()
             self.update_backlinks(source)
+        # Misc updates:
+        self.source_media_types.update(
+            [str(ref.media_type) for ref in source.reporef_list
+             if ref.media_type.is_custom()])       
+
+        attr_list = []
+        for mref in source.media_list:
+            attr_list += [str(attr.type) for attr in mref.attribute_list
+                          if attr.type.is_custom() and str(attr.type)]
+        self.media_attributes.update(attr_list)
+        self.source_attributes.update(
+            [str(attr.type) for attr in source.attribute_list
+             if attr.type.is_custom() and str(attr.type)])
         # Emit after added:
         if emit:
             self.emit(emit, ([source.handle],))
@@ -1496,6 +1620,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not trans.batch:
             self.dbapi.commit()
             self.update_backlinks(repository)
+        # Misc updates:
+        if repository.type.is_custom():
+            self.repository_types.add(str(repository.type))
+
+        self.url_types.update([str(url.type) for url in repository.urls
+                               if url.type.is_custom()])
         # Emit after added:
         if emit:
             self.emit(emit, ([repository.handle],))
@@ -1519,6 +1649,9 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not trans.batch:
             self.dbapi.commit()
             self.update_backlinks(note)
+        # Misc updates:
+        if note.type.is_custom():
+            self.note_types.add(str(note.type))        
         # Emit after added:
         if emit:
             self.emit(emit, ([note.handle],))
@@ -1547,6 +1680,18 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not trans.batch:
             self.dbapi.commit()
             self.update_backlinks(place)
+        # Misc updates:
+        if place.get_type().is_custom():
+            self.place_types.add(str(place.get_type()))
+
+        self.url_types.update([str(url.type) for url in place.urls
+                               if url.type.is_custom()])
+
+        attr_list = []
+        for mref in place.media_list:
+            attr_list += [str(attr.type) for attr in mref.attribute_list
+                          if attr.type.is_custom() and str(attr.type)]
+        self.media_attributes.update(attr_list)
         # Emit after added:
         if emit:
             self.emit(emit, ([place.handle],))
@@ -1569,18 +1714,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                        [event.handle, 
                         event.gramps_id, 
                         pickle.dumps(event.serialize())])
-
-        self.update_event_attributes(
-            [str(attr.type) for attr in event.attribute_list
-             if attr.type.is_custom() and str(attr.type)])
-
-        if event.type.is_custom():
-            self.update_event_names(str(event.type))
-
         if not trans.batch:
             self.dbapi.commit()
             self.update_backlinks(event)
-
+        # Misc updates:
+        self.event_attributes.update(
+            [str(attr.type) for attr in event.attribute_list
+             if attr.type.is_custom() and str(attr.type)])
+        if event.type.is_custom():
+            self.event_names.add(str(event.type))
+        attr_list = []
+        for mref in event.media_list:
+            attr_list += [str(attr.type) for attr in mref.attribute_list
+                          if attr.type.is_custom() and str(attr.type)]
+        self.media_attributes.update(attr_list)
         # Emit after added:
         if emit:
             self.emit(emit, ([event.handle],))
@@ -1657,6 +1804,10 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if not trans.batch:
             self.dbapi.commit()
             self.update_backlinks(media)
+        # Misc updates:
+        self.media_attributes.update(
+            [str(attr.type) for attr in media.attribute_list
+             if attr.type.is_custom() and str(attr.type)])
         # Emit after added:
         if emit:
             self.emit(emit, ([media.handle],))
@@ -1954,10 +2105,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def get_default_handle(self):
         return self.get_metadata("default-person-handle", None)
 
-    def find_place_child_handles(self, handle):
-        ## FIXME
-        return []
-
     def get_surname_list(self):
         """
         Return the list of locale-sorted surnames contained in the database.

From d38785740e7b99ace45367035d07518aeab97ed2 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Mon, 25 May 2015 21:06:44 -0400
Subject: [PATCH 098/105] DB-API: surname_list not working; added tag map
 support

---
 gramps/plugins/database/dbapi.py | 31 ++++++++++++++++++-------------
 1 file changed, 18 insertions(+), 13 deletions(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index c8673104f..36e4e6a51 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -10,7 +10,6 @@ import re
 import os
 import logging
 import shutil
-import bisect
 
 #------------------------------------------------------------------------
 #
@@ -463,8 +462,10 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 "handles_func": self.get_tag_handles,
                 "add_func": self.add_tag,
                 "commit_func": self.commit_tag,
+                "has_handle_func": self.has_handle_for_tag,
                 "iter_func": self.iter_tags,
                 "count": self.get_number_of_tags,
+                "raw_func": self._get_raw_tag_data,
             })
         # skip GEDCOM cross-ref check for now:
         self.set_feature("skip-check-xref", True)
@@ -1449,13 +1450,13 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         """
         if batch_transaction:
             return
-        name = conv_to_unicode(self._order_by_person_key(person), 'utf-8')
-        i = bisect.bisect(self.surname_list, name)
-        if 0 < i <= len(self.surname_list):
-            if self.surname_list[i-1] != name:
-                self.surname_list.insert(i, name)
-        else:
-            self.surname_list.insert(i, name)
+        #name = self._order_by_person_key(person)
+        #i = bisect.bisect(self.surname_list, name)
+        #if 0 < i <= len(self.surname_list):
+        #    if self.surname_list[i-1] != name:
+        #        self.surname_list.insert(i, name)
+        #else:
+        #    self.surname_list.insert(i, name)
 
     def remove_from_surname_list(self, person):
         """
@@ -1466,11 +1467,11 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         The function must be overridden in the derived class.
         """
         name = self._order_by_person_key(person)
-        if isinstance(name, str):
-            uname = name
-            name = name.encode('utf-8')
-        else:
-            uname = str(name)
+        #if isinstance(name, str):
+        #    uname = name
+        #    name = name.encode('utf-8')
+        #else:
+        #    uname = str(name)
         # FIXME: check database
 
     def commit_family(self, family, trans, change_time=None):
@@ -2615,6 +2616,10 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         cur = self.dbapi.execute("select * from note where handle = ?", [key])
         return cur.fetchone() != None
 
+    def has_handle_for_tag(self, key):
+        cur = self.dbapi.execute("select * from tag where handle = ?", [key])
+        return cur.fetchone() != None
+
     def has_gramps_id_for_person(self, key):
         cur = self.dbapi.execute("select * from person where gramps_id = ?", [key])
         return cur.fetchone() != None

From 1830e2b9430be674ee9d8a3f6243ddeaf5e422f2 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Mon, 25 May 2015 22:31:36 -0400
Subject: [PATCH 099/105] DB-API: sql clean up; some FIXME's still left

---
 gramps/plugins/database/dbapi.py | 286 +++++++++++++++++--------------
 1 file changed, 154 insertions(+), 132 deletions(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index 36e4e6a51..51a409240 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -10,6 +10,7 @@ import re
 import os
 import logging
 import shutil
+import bisect
 
 #------------------------------------------------------------------------
 #
@@ -865,12 +866,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.emit('home-person-changed')
 
     def get_name_group_keys(self):
-        cur = self.dbapi.execute("select name from name_group order by name;")
+        cur = self.dbapi.execute("SELECT name FROM name_group ORDER BY name;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_name_group_mapping(self, key):
-        cur = self.dbapi.execute("select grouping from name_group where name = ?;", 
+        cur = self.dbapi.execute("SELECT grouping FROM name_group WHERE name = ?;", 
                                  [key])
         row = cur.fetchone()
         if row:
@@ -885,62 +886,62 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return [row[0] for row in rows]
 
     def get_family_handles(self):
-        cur = self.dbapi.execute("select handle from family;")
+        cur = self.dbapi.execute("SELECT handle FROM family;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_event_handles(self):
-        cur = self.dbapi.execute("select handle from event;")
+        cur = self.dbapi.execute("SELECT handle FROM event;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_citation_handles(self, sort_handles=False):
         if sort_handles:
-            cur = self.dbapi.execute("select handle from citation ORDER BY order_by;")
+            cur = self.dbapi.execute("SELECT handle FROM citation ORDER BY order_by;")
         else:
-            cur = self.dbapi.execute("select handle from citation;")
+            cur = self.dbapi.execute("SELECT handle FROM citation;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_source_handles(self, sort_handles=False):
         if sort_handles:
-            cur = self.dbapi.execute("select handle from source ORDER BY order_by;")
+            cur = self.dbapi.execute("SELECT handle FROM source ORDER BY order_by;")
         else:
-            cur = self.dbapi.execute("select handle from source;")
+            cur = self.dbapi.execute("SELECT handle from source;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_place_handles(self, sort_handles=False):
         if sort_handles:
-            cur = self.dbapi.execute("select handle from place ORDER BY order_by;")
+            cur = self.dbapi.execute("SELECT handle FROM place ORDER BY order_by;")
         else:
-            cur = self.dbapi.execute("select handle from place;")
+            cur = self.dbapi.execute("SELECT handle FROM place;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_repository_handles(self):
-        cur = self.dbapi.execute("select handle from repository;")
+        cur = self.dbapi.execute("SELECT handle FROM repository;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_media_object_handles(self, sort_handles=False):
         if sort_handles:
-            cur = self.dbapi.execute("select handle from media ORDER BY order_by;")
+            cur = self.dbapi.execute("SELECT handle FROM media ORDER BY order_by;")
         else:
-            cur = self.dbapi.execute("select handle from media;")
+            cur = self.dbapi.execute("SELECT handle FROM media;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_note_handles(self):
-        cur = self.dbapi.execute("select handle from note;")
+        cur = self.dbapi.execute("SELECT handle FROM note;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_tag_handles(self, sort_handles=False):
         if sort_handles:
-            cur = self.dbapi.execute("select handle from tag ORDER BY order_by;")
+            cur = self.dbapi.execute("SELECT handle FROM tag ORDER BY order_by;")
         else:
-            cur = self.dbapi.execute("select handle from tag;")
+            cur = self.dbapi.execute("SELECT handle FROM tag;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
@@ -1097,52 +1098,52 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return None
 
     def get_number_of_people(self):
-        cur = self.dbapi.execute("select count(handle) from person;")
+        cur = self.dbapi.execute("SELECT count(handle) FROM person;")
         row = cur.fetchone()
         return row[0]
 
     def get_number_of_events(self):
-        cur = self.dbapi.execute("select count(handle) from event;")
+        cur = self.dbapi.execute("SELECT count(handle) FROM event;")
         row = cur.fetchone()
         return row[0]
 
     def get_number_of_places(self):
-        cur = self.dbapi.execute("select count(handle) from place;")
+        cur = self.dbapi.execute("SELECT count(handle) FROM place;")
         row = cur.fetchone()
         return row[0]
 
     def get_number_of_tags(self):
-        cur = self.dbapi.execute("select count(handle) from tag;")
+        cur = self.dbapi.execute("SELECT count(handle) FROM tag;")
         row = cur.fetchone()
         return row[0]
 
     def get_number_of_families(self):
-        cur = self.dbapi.execute("select count(handle) from family;")
+        cur = self.dbapi.execute("SELECT count(handle) FROM family;")
         row = cur.fetchone()
         return row[0]
 
     def get_number_of_notes(self):
-        cur = self.dbapi.execute("select count(handle) from note;")
+        cur = self.dbapi.execute("SELECT count(handle) FROM note;")
         row = cur.fetchone()
         return row[0]
 
     def get_number_of_citations(self):
-        cur = self.dbapi.execute("select count(handle) from citation;")
+        cur = self.dbapi.execute("SELECT count(handle) FROM citation;")
         row = cur.fetchone()
         return row[0]
 
     def get_number_of_sources(self):
-        cur = self.dbapi.execute("select count(handle) from source;")
+        cur = self.dbapi.execute("SELECT count(handle) FROM source;")
         row = cur.fetchone()
         return row[0]
 
     def get_number_of_media_objects(self):
-        cur = self.dbapi.execute("select count(handle) from media;")
+        cur = self.dbapi.execute("SELECT count(handle) FROM media;")
         row = cur.fetchone()
         return row[0]
 
     def get_number_of_repositories(self):
-        cur = self.dbapi.execute("select count(handle) from repository;")
+        cur = self.dbapi.execute("SELECT count(handle) FROM repository;")
         row = cur.fetchone()
         return row[0]
 
@@ -1221,20 +1222,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return handle in self.media_map
 
     def has_name_group_key(self, key):
-        cur = self.dbapi.execute("select grouping from name_group where name = ?;", 
+        cur = self.dbapi.execute("SELECT grouping FROM name_group WHERE name = ?;", 
                                  [key])
         row = cur.fetchone()
         return True if row else False
 
     def set_name_group_mapping(self, name, grouping):
         sname = name.encode("utf-8")
-        cur = self.dbapi.execute("SELECT * from name_group where name = ?;", 
+        cur = self.dbapi.execute("SELECT * FROM name_group WHERE name = ?;", 
                                  [sname])
         row = cur.fetchone()
         if row:
-            cur = self.dbapi.execute("DELETE from name_group where name = ?;", 
+            cur = self.dbapi.execute("DELETE FROM name_group WHERE name = ?;", 
                                      [sname])
-        cur = self.dbapi.execute("INSERT into name_group (name, grouping) VALUES(?, ?);",
+        cur = self.dbapi.execute("INSERT INTO name_group (name, grouping) VALUES(?, ?);",
                                  [sname, grouping])
         self.dbapi.commit()
 
@@ -1403,15 +1404,15 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.genderStats.count_person(person)
             self.add_to_surname_list(person, trans.batch)
             # Insert the person:
-            self.dbapi.execute("""insert into person (handle, order_by, gramps_id, blob)
-                            values(?, ?, ?, ?);""", 
+            self.dbapi.execute("""INSERT INTO person (handle, order_by, gramps_id, blob)
+                            VALUES(?, ?, ?, ?);""", 
                                [person.handle, 
                                 self._order_by_person_key(person),
                                 person.gramps_id, 
                                 pickle.dumps(person.serialize())])
         if not trans.batch:
-            self.dbapi.commit()
             self.update_backlinks(person)
+            self.dbapi.commit()
         # Other misc update tasks:
         self.individual_attributes.update(
             [str(attr.type) for attr in person.attribute_list
@@ -1450,13 +1451,21 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         """
         if batch_transaction:
             return
-        #name = self._order_by_person_key(person)
-        #i = bisect.bisect(self.surname_list, name)
-        #if 0 < i <= len(self.surname_list):
-        #    if self.surname_list[i-1] != name:
-        #        self.surname_list.insert(i, name)
-        #else:
-        #    self.surname_list.insert(i, name)
+        # TODO: check to see if this is correct
+        name = None
+        primary_name = person.get_primary_name()
+        if primary_name:
+            surname_list = primary_name.get_surname_list()
+            if len(surname_list) > 0:
+                name = surname_list[0].surname
+        if name is None:
+            return
+        i = bisect.bisect(self.surname_list, name)
+        if 0 < i <= len(self.surname_list):
+            if self.surname_list[i-1] != name:
+                self.surname_list.insert(i, name)
+        else:
+            self.surname_list.insert(i, name)
 
     def remove_from_surname_list(self, person):
         """
@@ -1466,13 +1475,16 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         If not then we need to remove the name from the list.
         The function must be overridden in the derived class.
         """
-        name = self._order_by_person_key(person)
-        #if isinstance(name, str):
-        #    uname = name
-        #    name = name.encode('utf-8')
-        #else:
-        #    uname = str(name)
-        # FIXME: check database
+        name = None
+        primary_name = person.get_primary_name()
+        if primary_name:
+            surname_list = primary_name.get_surname_list()
+            if len(surname_list) > 0:
+                name = surname_list[0].surname
+        if name is None:
+            return
+        if name in self.surname_list:
+            self.surname_list.remove(name)
 
     def commit_family(self, family, trans, change_time=None):
         emit = None
@@ -1486,13 +1498,13 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 family.handle])
         else:
             emit = "family-add"
-            self.dbapi.execute("""insert into family (handle, gramps_id, blob)
-                    values(?, ?, ?);""", 
+            self.dbapi.execute("""INSERT INTO family (handle, gramps_id, blob)
+                    VALUES(?, ?, ?);""", 
                                [family.handle, family.gramps_id, 
                                 pickle.dumps(family.serialize())])
         if not trans.batch:
-            self.dbapi.commit()
             self.update_backlinks(family)
+            self.dbapi.commit()
         # Misc updates:
         self.family_attributes.update(
             [str(attr.type) for attr in family.attribute_list
@@ -1537,15 +1549,15 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 citation.handle])
         else:
             emit = "citation-add"
-            self.dbapi.execute("""insert into citation (handle, order_by, gramps_id, blob)
-                     values(?, ?, ?, ?);""", 
+            self.dbapi.execute("""INSERT INTO citation (handle, order_by, gramps_id, blob)
+                     VALUES(?, ?, ?, ?);""", 
                        [citation.handle, 
                         self._order_by_citation_key(citation),
                         citation.gramps_id, 
                         pickle.dumps(citation.serialize())])
         if not trans.batch:
-            self.dbapi.commit()
             self.update_backlinks(citation)
+            self.dbapi.commit()
         # Misc updates:
         attr_list = []
         for mref in citation.media_list:
@@ -1576,15 +1588,15 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 source.handle])
         else:
             emit = "source-add"
-            self.dbapi.execute("""insert into source (handle, order_by, gramps_id, blob)
-                    values(?, ?, ?, ?);""", 
+            self.dbapi.execute("""INSERT INTO source (handle, order_by, gramps_id, blob)
+                    VALUES(?, ?, ?, ?);""", 
                        [source.handle, 
                         self._order_by_source_key(source),
                         source.gramps_id, 
                         pickle.dumps(source.serialize())])
         if not trans.batch:
-            self.dbapi.commit()
             self.update_backlinks(source)
+            self.dbapi.commit()
         # Misc updates:
         self.source_media_types.update(
             [str(ref.media_type) for ref in source.reporef_list
@@ -1615,12 +1627,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 repository.handle])
         else:
             emit = "repository-add"
-            self.dbapi.execute("""insert into repository (handle, gramps_id, blob)
-                     values(?, ?, ?);""", 
+            self.dbapi.execute("""INSERT INTO repository (handle, gramps_id, blob)
+                     VALUES(?, ?, ?);""", 
                        [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())])
         if not trans.batch:
-            self.dbapi.commit()
             self.update_backlinks(repository)
+            self.dbapi.commit()
         # Misc updates:
         if repository.type.is_custom():
             self.repository_types.add(str(repository.type))
@@ -1644,12 +1656,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 note.handle])
         else:
             emit = "note-add"
-            self.dbapi.execute("""insert into note (handle, gramps_id, blob)
-                     values(?, ?, ?);""", 
+            self.dbapi.execute("""INSERT INTO note (handle, gramps_id, blob)
+                     VALUES(?, ?, ?);""", 
                        [note.handle, note.gramps_id, pickle.dumps(note.serialize())])
         if not trans.batch:
-            self.dbapi.commit()
             self.update_backlinks(note)
+            self.dbapi.commit()
         # Misc updates:
         if note.type.is_custom():
             self.note_types.add(str(note.type))        
@@ -1672,15 +1684,15 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 place.handle])
         else:
             emit = "place-add"
-            self.dbapi.execute("""insert into place (handle, order_by, gramps_id, blob)
-                    values(?, ?, ?, ?);""", 
+            self.dbapi.execute("""INSERT INTO place (handle, order_by, gramps_id, blob)
+                    VALUES(?, ?, ?, ?);""", 
                        [place.handle, 
                         self._order_by_place_key(place),
                         place.gramps_id, 
                         pickle.dumps(place.serialize())])
         if not trans.batch:
-            self.dbapi.commit()
             self.update_backlinks(place)
+            self.dbapi.commit()
         # Misc updates:
         if place.get_type().is_custom():
             self.place_types.add(str(place.get_type()))
@@ -1710,14 +1722,14 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 event.handle])
         else:
             emit = "event-add"
-            self.dbapi.execute("""insert into event (handle, gramps_id, blob)
-                  values(?, ?, ?);""", 
+            self.dbapi.execute("""INSERT INTO event (handle, gramps_id, blob)
+                  VALUES(?, ?, ?);""", 
                        [event.handle, 
                         event.gramps_id, 
                         pickle.dumps(event.serialize())])
         if not trans.batch:
-            self.dbapi.commit()
             self.update_backlinks(event)
+            self.dbapi.commit()
         # Misc updates:
         self.event_attributes.update(
             [str(attr.type) for attr in event.attribute_list
@@ -1736,27 +1748,19 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def update_backlinks(self, obj):
         # First, delete the current references:
-        self.dbapi.execute("DELETE FROM reference where obj_handle = ?;",
+        self.dbapi.execute("DELETE FROM reference WHERE obj_handle = ?;",
                            [obj.handle])
         # Now, add the current ones:
         references = set(obj.get_referenced_handles_recursively())
         for (ref_class_name, ref_handle) in references:
-            self.dbapi.execute("""INSERT into reference 
+            self.dbapi.execute("""INSERT INTO reference 
                        (obj_handle, obj_class, ref_handle, ref_class)
                        VALUES(?, ?, ?, ?);""",
                                [obj.handle, 
                                 obj.__class__.__name__,
                                 ref_handle, 
                                 ref_class_name])
-        # Will commit later
-
-    def update_event_attributes(self, attr_list):
-        # FIXME
-        pass
-
-    def update_event_names(self, event_type):
-        # FIXME
-        pass
+        # This function is followed by a commit.
 
     def commit_tag(self, tag, trans, change_time=None):
         emit = None
@@ -1770,14 +1774,14 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 tag.handle])
         else:
             emit = "tag-add"
-            self.dbapi.execute("""insert into tag (handle, order_by, blob)
-                  values(?, ?, ?);""", 
+            self.dbapi.execute("""INSERT INTO tag (handle, order_by, blob)
+                  VALUES(?, ?, ?);""", 
                        [tag.handle, 
                         self._order_by_tag_key(tag),
                         pickle.dumps(tag.serialize())])
         if not trans.batch:
-            self.dbapi.commit()
             self.update_backlinks(tag)
+            self.dbapi.commit()
         # Emit after added:
         if emit:
             self.emit(emit, ([tag.handle],))
@@ -1796,15 +1800,15 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                                 media.handle])
         else:
             emit = "media-add"
-            self.dbapi.execute("""insert into media (handle, order_by, gramps_id, blob)
-                  values(?, ?, ?, ?);""", 
+            self.dbapi.execute("""INSERT INTO media (handle, order_by, gramps_id, blob)
+                  VALUES(?, ?, ?, ?);""", 
                        [media.handle, 
                         self._order_by_media_key(media),
                         media.gramps_id, 
                         pickle.dumps(media.serialize())])
         if not trans.batch:
-            self.dbapi.commit()
             self.update_backlinks(media)
+            self.dbapi.commit()
         # Misc updates:
         self.media_attributes.update(
             [str(attr.type) for attr in media.attribute_list
@@ -1910,7 +1914,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             person = Person.create(self.person_map[handle])
             #del self.person_map[handle]
             #del self.person_id_map[person.gramps_id]
-            self.dbapi.execute("DELETE from person WHERE handle = ?;", [handle])
+            self.dbapi.execute("DELETE FROM person WHERE handle = ?;", [handle])
             self.emit("person-delete", ([handle],))
             if not transaction.batch:
                 self.dbapi.commit()
@@ -2011,7 +2015,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         if self.readonly or not handle:
             return
         if handle in data_map:
-            self.dbapi.execute("DELETE from %s WHERE handle = ?;" % key2table[key], 
+            self.dbapi.execute("DELETE FROM %s WHERE handle = ?;" % key2table[key], 
                                [handle])
             self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],))
             if not transaction.batch:
@@ -2078,7 +2082,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
             result_list = list(find_backlink_handles(handle))
         """
-        cur = self.dbapi.execute("SELECT * from reference WHERE ref_handle = ?;",
+        cur = self.dbapi.execute("SELECT * FROM reference WHERE ref_handle = ?;",
                                  [handle])
         rows = cur.fetchall()
         for row in rows:
@@ -2553,7 +2557,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                     references = set(obj.get_referenced_handles_recursively())
                     # handle addition of new references
                     for (ref_class_name, ref_handle) in references:
-                        self.dbapi.execute("""INSERT into reference (obj_handle, obj_class, ref_handle, ref_class)
+                        self.dbapi.execute("""INSERT INTO reference (obj_handle, obj_class, ref_handle, ref_class)
                                                  VALUES(?, ?, ?, ?);""",
                                            [obj.handle, 
                                             obj.__class__.__name__,
@@ -2564,199 +2568,217 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         callback(5)
 
     def rebuild_secondary(self, update):
-        self.reindex_reference_map(update)
+        pass
+        # FIXME: rebuild the secondary databases/maps:
+        ## gender stats
+        ## event_names
+        ## fattr_names
+        ## pattr_names
+        ## sattr_names
+        ## marker_names
+        ## child_refs
+        ## family_rels
+        ## event_roles
+        ## name_types
+        ## origin_types
+        ## repo_types
+        ## note_types
+        ## sm_types
+        ## url_types
+        ## mattr_names
+        ## eattr_names
+        ## place_types
+        # surname list
 
     def prepare_import(self):
         """
-        DBAPI does not commit data on gedcom import, but saves them
-        for later commit.
+        Do anything needed before an import.
         """
         pass
 
     def commit_import(self):
         """
-        Commits the items that were queued up during the last gedcom
-        import for two step adding.
+        Do anything needed after an import.
         """
         self.reindex_reference_map(lambda n: n)
 
     def has_handle_for_person(self, key):
-        cur = self.dbapi.execute("select * from person where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM person WHERE handle = ?", [key])
         return cur.fetchone() != None
 
     def has_handle_for_family(self, key):
-        cur = self.dbapi.execute("select * from family where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM family WHERE handle = ?", [key])
         return cur.fetchone() != None
 
     def has_handle_for_source(self, key):
-        cur = self.dbapi.execute("select * from source where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM source WHERE handle = ?", [key])
         return cur.fetchone() != None
 
     def has_handle_for_citation(self, key):
-        cur = self.dbapi.execute("select * from citation where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM citation WHERE handle = ?", [key])
         return cur.fetchone() != None
 
     def has_handle_for_event(self, key):
-        cur = self.dbapi.execute("select * from event where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM event WHERE handle = ?", [key])
         return cur.fetchone() != None
 
     def has_handle_for_media(self, key):
-        cur = self.dbapi.execute("select * from media where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM media WHERE handle = ?", [key])
         return cur.fetchone() != None
 
     def has_handle_for_place(self, key):
-        cur = self.dbapi.execute("select * from place where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM place WHERE handle = ?", [key])
         return cur.fetchone() != None
 
     def has_handle_for_repository(self, key):
-        cur = self.dbapi.execute("select * from repository where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM repository WHERE handle = ?", [key])
         return cur.fetchone() != None
 
     def has_handle_for_note(self, key):
-        cur = self.dbapi.execute("select * from note where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM note WHERE handle = ?", [key])
         return cur.fetchone() != None
 
     def has_handle_for_tag(self, key):
-        cur = self.dbapi.execute("select * from tag where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM tag WHERE handle = ?", [key])
         return cur.fetchone() != None
 
     def has_gramps_id_for_person(self, key):
-        cur = self.dbapi.execute("select * from person where gramps_id = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM person WHERE gramps_id = ?", [key])
         return cur.fetchone() != None
 
     def has_gramps_id_for_family(self, key):
-        cur = self.dbapi.execute("select * from family where gramps_id = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM family WHERE gramps_id = ?", [key])
         return cur.fetchone() != None
 
     def has_gramps_id_for_source(self, key):
-        cur = self.dbapi.execute("select * from source where gramps_id = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM source WHERE gramps_id = ?", [key])
         return cur.fetchone() != None
 
     def has_gramps_id_for_citation(self, key):
-        cur = self.dbapi.execute("select * from citation where gramps_id = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM citation WHERE gramps_id = ?", [key])
         return cur.fetchone() != None
 
     def has_gramps_id_for_event(self, key):
-        cur = self.dbapi.execute("select * from event where gramps_id = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM event WHERE gramps_id = ?", [key])
         return cur.fetchone() != None
 
     def has_gramps_id_for_media(self, key):
-        cur = self.dbapi.execute("select * from media where gramps_id = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM media WHERE gramps_id = ?", [key])
         return cur.fetchone() != None
 
     def has_gramps_id_for_place(self, key):
-        cur = self.dbapi.execute("select * from place where gramps_id = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM place WHERE gramps_id = ?", [key])
         return cur.fetchone() != None
 
     def has_gramps_id_for_repository(self, key):
-        cur = self.dbapi.execute("select * from repository where gramps_id = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM repository WHERE gramps_id = ?", [key])
         return cur.fetchone() != None
 
     def has_gramps_id_for_note(self, key):
-        cur = self.dbapi.execute("select * from note where gramps_id = ?", [key])
+        cur = self.dbapi.execute("SELECT * FROM note WHERE gramps_id = ?", [key])
         return cur.fetchone() != None
 
     def get_person_gramps_ids(self):
-        cur = self.dbapi.execute("select gramps_id from person;")
+        cur = self.dbapi.execute("SELECT gramps_id FROM person;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_family_gramps_ids(self):
-        cur = self.dbapi.execute("select gramps_id from family;")
+        cur = self.dbapi.execute("SELECT gramps_id FROM family;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_source_gramps_ids(self):
-        cur = self.dbapi.execute("select gramps_id from source;")
+        cur = self.dbapi.execute("SELECT gramps_id FROM source;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_citation_gramps_ids(self):
-        cur = self.dbapi.execute("select gramps_id from citation;")
+        cur = self.dbapi.execute("SELECT gramps_id FROM citation;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_event_gramps_ids(self):
-        cur = self.dbapi.execute("select gramps_id from event;")
+        cur = self.dbapi.execute("SELECT gramps_id FROM event;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_media_gramps_ids(self):
-        cur = self.dbapi.execute("select gramps_id from media;")
+        cur = self.dbapi.execute("SELECT gramps_id FROM media;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_place_gramps_ids(self):
-        cur = self.dbapi.execute("select gramps from place;")
+        cur = self.dbapi.execute("SELECT gramps FROM place;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_repository_gramps_ids(self):
-        cur = self.dbapi.execute("select gramps_id from repository;")
+        cur = self.dbapi.execute("SELECT gramps_id FROM repository;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def get_note_gramps_ids(self):
-        cur = self.dbapi.execute("select gramps_id from note;")
+        cur = self.dbapi.execute("SELECT gramps_id FROM note;")
         rows = cur.fetchall()
         return [row[0] for row in rows]
 
     def _get_raw_person_data(self, key):
-        cur = self.dbapi.execute("select blob from person where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT blob FROM person WHERE handle = ?", [key])
         row = cur.fetchone()
         if row:
             return pickle.loads(row[0])
 
     def _get_raw_family_data(self, key):
-        cur = self.dbapi.execute("select blob from family where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT blob FROM family WHERE handle = ?", [key])
         row = cur.fetchone()
         if row:
             return pickle.loads(row[0])
 
     def _get_raw_source_data(self, key):
-        cur = self.dbapi.execute("select blob from source where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT blob FROM source WHERE handle = ?", [key])
         row = cur.fetchone()
         if row:
             return pickle.loads(row[0])
 
     def _get_raw_citation_data(self, key):
-        cur = self.dbapi.execute("select blob from citation where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT blob FROM citation WHERE handle = ?", [key])
         row = cur.fetchone()
         if row:
             return pickle.loads(row[0])
 
     def _get_raw_event_data(self, key):
-        cur = self.dbapi.execute("select blob from event where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT blob FROM event WHERE handle = ?", [key])
         row = cur.fetchone()
         if row:
             return pickle.loads(row[0])
 
     def _get_raw_media_data(self, key):
-        cur = self.dbapi.execute("select blob from media where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT blob FROM media WHERE handle = ?", [key])
         row = cur.fetchone()
         if row:
             return pickle.loads(row[0])
 
     def _get_raw_place_data(self, key):
-        cur = self.dbapi.execute("select blob from place where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT blob FROM place WHERE handle = ?", [key])
         row = cur.fetchone()
         if row:
             return pickle.loads(row[0])
 
     def _get_raw_repository_data(self, key):
-        cur = self.dbapi.execute("select blob from repository where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT blob FROM repository WHERE handle = ?", [key])
         row = cur.fetchone()
         if row:
             return pickle.loads(row[0])
 
     def _get_raw_note_data(self, key):
-        cur = self.dbapi.execute("select blob from note where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT blob FROM note WHERE handle = ?", [key])
         row = cur.fetchone()
         if row:
             return pickle.loads(row[0])
 
     def _get_raw_tag_data(self, key):
-        cur = self.dbapi.execute("select blob from tag where handle = ?", [key])
+        cur = self.dbapi.execute("SELECT blob FROM tag WHERE handle = ?", [key])
         row = cur.fetchone()
         if row:
             return pickle.loads(row[0])

From 8551a0b071a1ce3d83a4589341b0dedcf416cc3b Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Tue, 26 May 2015 11:35:56 -0400
Subject: [PATCH 100/105] DB-API: fixed error in find_initial_person

---
 gramps/plugins/database/dbapi.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index 51a409240..54ca72cea 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -2099,7 +2099,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         cur = self.dbapi.execute("SELECT handle FROM person;")
         row = cur.fetchone()
         if row:
-            return row[0]
+            return self.get_person_from_handle(row[0])
 
     def get_bookmarks(self):
         return self.bookmarks

From a213f92f0d86e85e416b4f9a4968ed81fdf4b71d Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 30 May 2015 07:22:30 -0400
Subject: [PATCH 101/105] DB-API: support dbdid

---
 gramps/plugins/database/dbapi.py | 25 +++++++++++++++++--------
 1 file changed, 17 insertions(+), 8 deletions(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index 54ca72cea..17aa99d02 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -468,6 +468,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
                 "count": self.get_number_of_tags,
                 "raw_func": self._get_raw_tag_data,
             })
+        self.set_save_path(directory)
         # skip GEDCOM cross-ref check for now:
         self.set_feature("skip-check-xref", True)
         self.set_feature("skip-import-additions", True)
@@ -560,11 +561,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.transaction = None
         self.abort_possible = False
         self._bm_changes = 0
-        self._directory = directory
         self._has_changed = False
-        self.full_name = None
-        self.path = None
-        self.brief_name = None
         self.genderStats = GenderStats() # can pass in loaded stats as dict
         self.owner = Researcher()
         if directory:
@@ -2455,7 +2452,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # surname list
         self.surname_list = self.get_metadata('surname_list')
 
-        self._directory = directory
+        self.set_save_path(directory)
         self.undolog = os.path.join(self._directory, DBUNDOFN)
         self.undodb = DBAPIUndo(self, self.undolog)
         self.undodb.open()
@@ -2474,9 +2471,14 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def set_save_path(self, directory):
         self._directory = directory
-        self.full_name = os.path.abspath(self._directory)
-        self.path = self.full_name
-        self.brief_name = os.path.basename(self._directory)
+        if directory:
+            self.full_name = os.path.abspath(self._directory)
+            self.path = self.full_name
+            self.brief_name = os.path.basename(self._directory)
+        else:
+            self.full_name = ""
+            self.path = ""
+            self.brief_name = ""
 
     def write_version(self, directory):
         """Write files for a newly created DB."""
@@ -2832,3 +2834,10 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def redo(self, update_history=True):
         return self.undodb.redo(update_history)
+
+    def get_dbid(self):
+        """
+        We use the file directory name as the unique ID for
+        this database on this computer.
+        """
+        return self.brief_name

From 5b2bc78108a1b4d3aacdbd60e940609839a0e9ba Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 30 May 2015 07:54:59 -0400
Subject: [PATCH 102/105] DB-API: allow low-level map import

---
 gramps/plugins/database/dbapi.py | 60 ++++++++++++++++++++------------
 1 file changed, 38 insertions(+), 22 deletions(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index 17aa99d02..5b7d9b032 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -131,8 +131,13 @@ class Table(object):
     """
     Implements Table interface.
     """
-    def __init__(self, funcs):
-        self.funcs = funcs
+    def __init__(self, db, table_name, funcs=None):
+        self.db = db
+        self.table_name = table_name
+        if funcs:
+            self.funcs = funcs
+        else:
+            self.funcs = db._tables[table_name]
 
     def cursor(self):
         """
@@ -157,6 +162,7 @@ class Map(object):
         self.table = table
         self.keys_func = keys_func
         self.contains_func = contains_func
+        self.txn = DBAPITxn("Dummy transaction", db=self.table.db, batch=True)
 
     def keys(self):
         return self.table.funcs[self.keys_func]()
@@ -171,6 +177,16 @@ class Map(object):
         if self.table.funcs[self.contains_func](key):
             return self.table.funcs["raw_func"](key)
 
+    def __setitem__(self, key, value):
+        """
+        This is only done in a low-level raw import.
+
+        value: serialized object
+        key: bytes key (ignored in this implementation)
+        """
+        obj = self.table.funcs["class_func"].create(value)
+        self.table.funcs["commit_func"](obj, self.txn)
+
     def __len__(self):
         return self.table.funcs["count_func"]()
 
@@ -515,44 +531,44 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.rmap_index = 0
         self.nmap_index = 0
         self.env = Environment(self)
-        self.person_map = Map(Table(self._tables["Person"]))
-        self.person_id_map = Map(Table(self._tables["Person"]),
+        self.person_map = Map(Table(self, "Person"))
+        self.person_id_map = Map(Table(self, "Person"),
                                  keys_func="ids_func",
                                  contains_func="has_gramps_id_func")
-        self.family_map = Map(Table(self._tables["Family"]))
-        self.family_id_map = Map(Table(self._tables["Family"]),
+        self.family_map = Map(Table(self, "Family"))
+        self.family_id_map = Map(Table(self, "Family"),
                                  keys_func="ids_func",
                                  contains_func="has_gramps_id_func")
-        self.place_map  = Map(Table(self._tables["Place"]))
-        self.place_id_map = Map(Table(self._tables["Place"]),
+        self.place_map  = Map(Table(self, "Place"))
+        self.place_id_map = Map(Table(self, "Place"),
                                  keys_func="ids_func",
                                  contains_func="has_gramps_id_func")
-        self.citation_map = Map(Table(self._tables["Citation"]))
-        self.citation_id_map = Map(Table(self._tables["Citation"]),
+        self.citation_map = Map(Table(self, "Citation"))
+        self.citation_id_map = Map(Table(self, "Citation"),
                                  keys_func="ids_func",
                                  contains_func="has_gramps_id_func")
-        self.source_map = Map(Table(self._tables["Source"]))
-        self.source_id_map = Map(Table(self._tables["Source"]),
+        self.source_map = Map(Table(self, "Source"))
+        self.source_id_map = Map(Table(self, "Source"),
                                  keys_func="ids_func",
                                 contains_func="has_gramps_id_func")
-        self.repository_map  = Map(Table(self._tables["Repository"]))
-        self.repository_id_map = Map(Table(self._tables["Repository"]),
+        self.repository_map  = Map(Table(self, "Repository"))
+        self.repository_id_map = Map(Table(self, "Repository"),
                                  keys_func="ids_func",
                                  contains_func="has_gramps_id_func")
-        self.note_map = Map(Table(self._tables["Note"]))
-        self.note_id_map = Map(Table(self._tables["Note"]),
+        self.note_map = Map(Table(self, "Note"))
+        self.note_id_map = Map(Table(self, "Note"),
                                  keys_func="ids_func",
                                  contains_func="has_gramps_id_func")
-        self.media_map  = Map(Table(self._tables["Media"]))
-        self.media_id_map = Map(Table(self._tables["Media"]),
+        self.media_map  = Map(Table(self, "Media"))
+        self.media_id_map = Map(Table(self, "Media"),
                                  keys_func="ids_func",
                                  contains_func="has_gramps_id_func")
-        self.event_map  = Map(Table(self._tables["Event"]))
-        self.event_id_map = Map(Table(self._tables["Event"]), 
+        self.event_map  = Map(Table(self, "Event"))
+        self.event_id_map = Map(Table(self, "Event"), 
                                  keys_func="ids_func",
                                  contains_func="has_gramps_id_func")
-        self.tag_map  = Map(Table(self._tables["Tag"]))
-        self.metadata   = Map(Table({"cursor_func": lambda: MetaCursor()}))
+        self.tag_map  = Map(Table(self, "Tag"))
+        self.metadata   = Map(Table(self, "Metadata", funcs={"cursor_func": lambda: MetaCursor()}))
         self.undo_callback = None
         self.redo_callback = None
         self.undo_history_callback = None

From 0b2ad1df3540ba0b8d10057236ae877298e23bde Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Sat, 30 May 2015 08:19:13 -0400
Subject: [PATCH 103/105] DB-API: only backup when changes; fixed has_changed

---
 gramps/plugins/database/dbapi.py | 38 ++++++++++++++------------------
 1 file changed, 17 insertions(+), 21 deletions(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index 5b7d9b032..272e713a6 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -577,7 +577,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         self.transaction = None
         self.abort_possible = False
         self._bm_changes = 0
-        self._has_changed = False
+        self.has_changed = False
         self.genderStats = GenderStats() # can pass in loaded stats as dict
         self.owner = Researcher()
         if directory:
@@ -1456,7 +1456,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # Emit after added:
         if emit:
             self.emit(emit, ([person.handle],))
-        self._has_changed = True
+        self.has_changed = True
 
     def add_to_surname_list(self, person, batch_transaction):
         """
@@ -1546,7 +1546,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # Emit after added:
         if emit:
             self.emit(emit, ([family.handle],))
-        self._has_changed = True
+        self.has_changed = True
 
     def commit_citation(self, citation, trans, change_time=None):
         emit = None
@@ -1585,7 +1585,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # Emit after added:
         if emit:
             self.emit(emit, ([citation.handle],))
-        self._has_changed = True
+        self.has_changed = True
 
     def commit_source(self, source, trans, change_time=None):
         emit = None
@@ -1626,7 +1626,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # Emit after added:
         if emit:
             self.emit(emit, ([source.handle],))
-        self._has_changed = True
+        self.has_changed = True
 
     def commit_repository(self, repository, trans, change_time=None):
         emit = None
@@ -1655,7 +1655,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # Emit after added:
         if emit:
             self.emit(emit, ([repository.handle],))
-        self._has_changed = True
+        self.has_changed = True
 
     def commit_note(self, note, trans, change_time=None):
         emit = None
@@ -1681,7 +1681,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # Emit after added:
         if emit:
             self.emit(emit, ([note.handle],))
-        self._has_changed = True
+        self.has_changed = True
 
     def commit_place(self, place, trans, change_time=None):
         emit = None
@@ -1721,7 +1721,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # Emit after added:
         if emit:
             self.emit(emit, ([place.handle],))
-        self._has_changed = True
+        self.has_changed = True
 
     def commit_event(self, event, trans, change_time=None):
         emit = None
@@ -1757,7 +1757,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         # Emit after added:
         if emit:
             self.emit(emit, ([event.handle],))
-        self._has_changed = True
+        self.has_changed = True
 
     def update_backlinks(self, obj):
         # First, delete the current references:
@@ -2036,11 +2036,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
 
     def close(self):
         if self._directory:
-            from gramps.plugins.export.exportxml import XmlWriter
-            from gramps.cli.user import User 
-            writer = XmlWriter(self, User(), strip_photos=0, compress=1)
-            filename = os.path.join(self._directory, "data.gramps")
-            writer.write(filename)
             filename = os.path.join(self._directory, "meta_data.db")
             touch(filename)
             # Save metadata
@@ -2276,9 +2271,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
     def get_source_bookmarks(self):
         return self.source_bookmarks
 
-    def has_changed(self):
-        return self._has_changed
-
     def is_open(self):
         return self._directory is not None
 
@@ -2492,9 +2484,9 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
             self.path = self.full_name
             self.brief_name = os.path.basename(self._directory)
         else:
-            self.full_name = ""
-            self.path = ""
-            self.brief_name = ""
+            self.full_name = None
+            self.path = None
+            self.brief_name = None
 
     def write_version(self, directory):
         """Write files for a newly created DB."""
@@ -2834,7 +2826,11 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         """
         If you wish to support an optional backup routine, put it here.
         """
-        pass
+        from gramps.plugins.export.exportxml import XmlWriter
+        from gramps.cli.user import User 
+        writer = XmlWriter(self, User(), strip_photos=0, compress=1)
+        filename = os.path.join(self._directory, "data.gramps")
+        writer.write(filename)
 
     def restore(self):
         """

From 4ee8ac258518077bc9a486b5b70738ca0b07be88 Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Mon, 1 Jun 2015 21:13:38 -0400
Subject: [PATCH 104/105] DB-API: Sped up get_tag_from_name

---
 gramps/plugins/database/dbapi.py | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
index 272e713a6..5b1485d50 100644
--- a/gramps/plugins/database/dbapi.py
+++ b/gramps/plugins/database/dbapi.py
@@ -1058,11 +1058,11 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
         return (handle for handle in self.family_map.keys())
 
     def get_tag_from_name(self, name):
-        ## FIXME: Slow, but typically not too many tags:
-        for data in self.tag_map.values():
-            tag = Tag.create(data)
-            if tag.name == name:
-                return tag
+        cur = self.dbapi.execute("""select handle from tag where order_by = ?;""",
+                                 [self._order_by_tag_key(name)])
+        row = cur.fetchone()
+        if row:
+            self.get_tag_from_handle(row[0])
         return None
 
     def get_person_from_gramps_id(self, gramps_id):

From 7bcb6295471629c5c524dab2bdf0d62257bec6ff Mon Sep 17 00:00:00 2001
From: Doug Blank <dblank@cs.brynmawr.edu>
Date: Tue, 2 Jun 2015 10:55:38 -0400
Subject: [PATCH 105/105] Updated backends to 5.0; moved Django and DBAPI to
 addons

---
 gramps/plugins/database/bsddb.gpr.py          |    2 +-
 gramps/plugins/database/dbapi.gpr.py          |   31 -
 gramps/plugins/database/dbapi.py              | 2855 -----------------
 .../defaults/default_settings.py              |    7 -
 gramps/plugins/database/dictionarydb.gpr.py   |    2 +-
 .../defaults/default_settings.py              |  150 -
 .../django_support/defaults/sqlite.db         |  Bin 293888 -> 0 bytes
 .../database/django_support/libdjango.py      | 2063 ------------
 gramps/plugins/database/djangodb.gpr.py       |   32 -
 gramps/plugins/database/djangodb.py           | 2150 -------------
 10 files changed, 2 insertions(+), 7290 deletions(-)
 delete mode 100644 gramps/plugins/database/dbapi.gpr.py
 delete mode 100644 gramps/plugins/database/dbapi.py
 delete mode 100644 gramps/plugins/database/dbapi_support/defaults/default_settings.py
 delete mode 100644 gramps/plugins/database/django_support/defaults/default_settings.py
 delete mode 100644 gramps/plugins/database/django_support/defaults/sqlite.db
 delete mode 100644 gramps/plugins/database/django_support/libdjango.py
 delete mode 100644 gramps/plugins/database/djangodb.gpr.py
 delete mode 100644 gramps/plugins/database/djangodb.py

diff --git a/gramps/plugins/database/bsddb.gpr.py b/gramps/plugins/database/bsddb.gpr.py
index 20f2e9fda..82a23573c 100644
--- a/gramps/plugins/database/bsddb.gpr.py
+++ b/gramps/plugins/database/bsddb.gpr.py
@@ -24,7 +24,7 @@ plg.name  = _("BSDDB Database Backend")
 plg.name_accell  = _("_BSDDB Database Backend")
 plg.description =  _("Berkeley Software Distribution Database Backend")
 plg.version = '1.0'
-plg.gramps_target_version = "4.2"
+plg.gramps_target_version = "5.0"
 plg.status = STABLE
 plg.fname = 'bsddb.py'
 plg.ptype = DATABASE
diff --git a/gramps/plugins/database/dbapi.gpr.py b/gramps/plugins/database/dbapi.gpr.py
deleted file mode 100644
index 2749e7a50..000000000
--- a/gramps/plugins/database/dbapi.gpr.py
+++ /dev/null
@@ -1,31 +0,0 @@
-#
-# Gramps - a GTK+/GNOME based genealogy program
-#
-# Copyright (C) 2015 Douglas Blank <doug.blank@gmail.com>
-#
-# 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; either version 2 of the License, or
-# (at your option) any later version.
-#
-# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-
-plg = newplugin()
-plg.id    = 'dbapi'
-plg.name  = _("DB-API 2.0")
-plg.name_accell  = _("DB-_API 2.0")
-plg.description =  _("DB-API 2.0 Database Backend")
-plg.version = '1.0'
-plg.gramps_target_version = "4.2"
-plg.status = STABLE
-plg.fname = 'dbapi.py'
-plg.ptype = DATABASE
-plg.databaseclass = 'DBAPI'
diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py
deleted file mode 100644
index 5b1485d50..000000000
--- a/gramps/plugins/database/dbapi.py
+++ /dev/null
@@ -1,2855 +0,0 @@
-#------------------------------------------------------------------------
-#
-# Python Modules
-#
-#------------------------------------------------------------------------
-import pickle
-import base64
-import time
-import re
-import os
-import logging
-import shutil
-import bisect
-
-#------------------------------------------------------------------------
-#
-# Gramps Modules
-#
-#------------------------------------------------------------------------
-import gramps
-from gramps.gen.const import GRAMPS_LOCALE as glocale
-_ = glocale.translation.gettext
-from gramps.gen.db import (DbReadBase, DbWriteBase, DbTxn, DbUndo,
-                           KEY_TO_NAME_MAP, KEY_TO_CLASS_MAP, 
-                           CLASS_TO_KEY_MAP)
-from gramps.gen.utils.callback import Callback
-from gramps.gen.updatecallback import UpdateCallback
-from gramps.gen.db.dbconst import *
-from gramps.gen.db import (PERSON_KEY,
-                           FAMILY_KEY,
-                           CITATION_KEY,
-                           SOURCE_KEY,
-                           EVENT_KEY,
-                           MEDIA_KEY,
-                           PLACE_KEY,
-                           REPOSITORY_KEY,
-                           NOTE_KEY,
-                           TAG_KEY)
-
-from gramps.gen.utils.id import create_id
-from gramps.gen.lib.researcher import Researcher
-from gramps.gen.lib import (Tag, MediaObject, Person, Family, Source, Citation, Event,
-                            Place, Repository, Note, NameOriginType)
-from gramps.gen.lib.genderstats import GenderStats
-
-_LOG = logging.getLogger(DBLOGNAME)
-
-def touch(fname, mode=0o666, dir_fd=None, **kwargs):
-    ## After http://stackoverflow.com/questions/1158076/implement-touch-using-python
-    flags = os.O_CREAT | os.O_APPEND
-    with os.fdopen(os.open(fname, flags=flags, mode=mode, dir_fd=dir_fd)) as f:
-        os.utime(f.fileno() if os.utime in os.supports_fd else fname,
-                 dir_fd=None if os.supports_fd else dir_fd, **kwargs)
-
-class DBAPIUndo(DbUndo):
-    def __init__(self, grampsdb, path):
-        super(DBAPIUndo, self).__init__(grampsdb)
-        self.undodb = grampsdb
-        self.path = path
-
-    def open(self, value=None):
-        """
-        Open the backing storage.  Needs to be overridden in the derived
-        class.
-        """
-        pass
-        # FIXME
-
-    def close(self):
-        """
-        Close the backing storage.  Needs to be overridden in the derived
-        class.
-        """        
-        pass
-        # FIXME
-
-    def append(self, value):
-        """
-        Add a new entry on the end.  Needs to be overridden in the derived
-        class.
-        """        
-        pass
-        # FIXME
-
-    def __getitem__(self, index):
-        """
-        Returns an entry by index number.  Needs to be overridden in the
-        derived class.
-        """        
-        return None
-        # FIXME
-
-    def __setitem__(self, index, value):
-        """
-        Set an entry to a value.  Needs to be overridden in the derived class.
-        """           
-        pass
-        # FIXME
-
-    def __len__(self):
-        """
-        Returns the number of entries.  Needs to be overridden in the derived
-        class.
-        """         
-        return 0
-        # FIXME
-
-    def __redo(self, update_history):
-        """
-        """
-        pass
-        # FIXME
-
-    def __undo(self, update_history):
-        """
-        """
-        pass
-        # FIXME
-
-class Environment(object):
-    """
-    Implements the Environment API.
-    """
-    def __init__(self, db):
-        self.db = db
-
-    def txn_begin(self):
-        return DBAPITxn("DBAPIDb Transaction", self.db)
-
-class Table(object):
-    """
-    Implements Table interface.
-    """
-    def __init__(self, db, table_name, funcs=None):
-        self.db = db
-        self.table_name = table_name
-        if funcs:
-            self.funcs = funcs
-        else:
-            self.funcs = db._tables[table_name]
-
-    def cursor(self):
-        """
-        Returns a Cursor for this Table.
-        """
-        return self.funcs["cursor_func"]()
-
-    def put(self, key, data, txn=None):
-        self.funcs["add_func"](data, txn)
-
-class Map(object):
-    """
-    Implements the map API for person_map, etc.
-    
-    Takes a Table() as argument.
-    """
-    def __init__(self, table, 
-                 keys_func="handles_func", 
-                 contains_func="has_handle_func",
-                 *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self.table = table
-        self.keys_func = keys_func
-        self.contains_func = contains_func
-        self.txn = DBAPITxn("Dummy transaction", db=self.table.db, batch=True)
-
-    def keys(self):
-        return self.table.funcs[self.keys_func]()
-
-    def values(self):
-        return self.table.funcs["cursor_func"]()
-
-    def __contains__(self, key):
-        return self.table.funcs[self.contains_func](key)
-
-    def __getitem__(self, key):
-        if self.table.funcs[self.contains_func](key):
-            return self.table.funcs["raw_func"](key)
-
-    def __setitem__(self, key, value):
-        """
-        This is only done in a low-level raw import.
-
-        value: serialized object
-        key: bytes key (ignored in this implementation)
-        """
-        obj = self.table.funcs["class_func"].create(value)
-        self.table.funcs["commit_func"](obj, self.txn)
-
-    def __len__(self):
-        return self.table.funcs["count_func"]()
-
-class MetaCursor(object):
-    def __init__(self):
-        pass
-    def __enter__(self):
-        return self
-    def __iter__(self):
-        return self.__next__()
-    def __next__(self):
-        yield None
-    def __exit__(self, *args, **kwargs):
-        pass
-    def iter(self):
-        yield None
-    def first(self):
-        self._iter = self.__iter__()
-        return self.next()
-    def next(self):
-        try:
-            return next(self._iter)
-        except:
-            return None
-    def close(self):
-        pass
-
-class Cursor(object):
-    def __init__(self, map):
-        self.map = map
-        self._iter = self.__iter__()
-    def __enter__(self):
-        return self
-    def __iter__(self):
-        for item in self.map.keys():
-            yield (bytes(item, "utf-8"), self.map[item])
-    def __next__(self):
-        try:
-            return self._iter.__next__()
-        except StopIteration:
-            return None
-    def __exit__(self, *args, **kwargs):
-        pass
-    def iter(self):
-        for item in self.map.keys():
-            yield (bytes(item, "utf-8"), self.map[item])
-    def first(self):
-        self._iter = self.__iter__()
-        try:
-            return next(self._iter)
-        except:
-            return
-    def next(self):
-        try:
-            return next(self._iter)
-        except:
-            return
-    def close(self):
-        pass
-
-class Bookmarks(object):
-    def __init__(self, default=[]):
-        self.handles = list(default)
-
-    def set(self, handles):
-        self.handles = list(handles)
-
-    def get(self):
-        return self.handles
-
-    def append(self, handle):
-        self.handles.append(handle)
-
-    def append_list(self, handles):
-        self.handles += handles
-
-    def remove(self, handle):
-        self.handles.remove(handle)
-
-    def pop(self, item):
-        return self.handles.pop(item)
-
-    def insert(self, pos, item):
-        self.handles.insert(pos, item)
-
-    def close(self):
-        del self.handles
-    
-
-class DBAPITxn(DbTxn):
-    def __init__(self, message, db, batch=False):
-        DbTxn.__init__(self, message, db, batch)
-
-    def get(self, key, default=None, txn=None, **kwargs):
-        """
-        Returns the data object associated with key
-        """
-        if txn and key in txn:
-            return txn[key]
-        else:
-            return None
-
-    def put(self, handle, new_data, txn):
-        """
-        """
-        txn[handle] = new_data
-
-class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
-    """
-    A Gramps Database Backend. This replicates the grampsdb functions.
-    """
-    __signals__ = dict((obj+'-'+op, signal)
-                       for obj in
-                       ['person', 'family', 'event', 'place',
-                        'source', 'citation', 'media', 'note', 'repository', 'tag']
-                       for op, signal in zip(
-                               ['add',   'update', 'delete', 'rebuild'],
-                               [(list,), (list,),  (list,),   None]
-                       )
-                   )
-    
-    # 2. Signals for long operations
-    __signals__.update(('long-op-'+op, signal) for op, signal in zip(
-        ['start',  'heartbeat', 'end'],
-        [(object,), None,       None]
-        ))
-
-    # 3. Special signal for change in home person
-    __signals__['home-person-changed'] = None
-
-    # 4. Signal for change in person group name, parameters are
-    __signals__['person-groupname-rebuild'] = (str, str)
-
-    __callback_map = {}
-
-    def __init__(self, directory=None):
-        DbReadBase.__init__(self)
-        DbWriteBase.__init__(self)
-        Callback.__init__(self)
-        self._tables['Person'].update(
-            {
-                "handle_func": self.get_person_from_handle, 
-                "gramps_id_func": self.get_person_from_gramps_id,
-                "class_func": Person,
-                "cursor_func": self.get_person_cursor,
-                "handles_func": self.get_person_handles,
-                "add_func": self.add_person,
-                "commit_func": self.commit_person,
-                "iter_func": self.iter_people,
-                "ids_func": self.get_person_gramps_ids,
-                "has_handle_func": self.has_handle_for_person,
-                "has_gramps_id_func": self.has_gramps_id_for_person,
-                "count": self.get_number_of_people,
-                "raw_func": self._get_raw_person_data,
-            })
-        self._tables['Family'].update(
-            {
-                "handle_func": self.get_family_from_handle, 
-                "gramps_id_func": self.get_family_from_gramps_id,
-                "class_func": Family,
-                "cursor_func": self.get_family_cursor,
-                "handles_func": self.get_family_handles,
-                "add_func": self.add_family,
-                "commit_func": self.commit_family,
-                "iter_func": self.iter_families,
-                "ids_func": self.get_family_gramps_ids,
-                "has_handle_func": self.has_handle_for_family,
-                "has_gramps_id_func": self.has_gramps_id_for_family,
-                "count": self.get_number_of_families,
-                "raw_func": self._get_raw_family_data,
-            })
-        self._tables['Source'].update(
-            {
-                "handle_func": self.get_source_from_handle, 
-                "gramps_id_func": self.get_source_from_gramps_id,
-                "class_func": Source,
-                "cursor_func": self.get_source_cursor,
-                "handles_func": self.get_source_handles,
-                "add_func": self.add_source,
-                "commit_func": self.commit_source,
-                "iter_func": self.iter_sources,
-                "ids_func": self.get_source_gramps_ids,
-                "has_handle_func": self.has_handle_for_source,
-                "has_gramps_id_func": self.has_gramps_id_for_source,
-                "count": self.get_number_of_sources,
-                "raw_func": self._get_raw_source_data,
-                })
-        self._tables['Citation'].update(
-            {
-                "handle_func": self.get_citation_from_handle, 
-                "gramps_id_func": self.get_citation_from_gramps_id,
-                "class_func": Citation,
-                "cursor_func": self.get_citation_cursor,
-                "handles_func": self.get_citation_handles,
-                "add_func": self.add_citation,
-                "commit_func": self.commit_citation,
-                "iter_func": self.iter_citations,
-                "ids_func": self.get_citation_gramps_ids,
-                "has_handle_func": self.has_handle_for_citation,
-                "has_gramps_id_func": self.has_gramps_id_for_citation,
-                "count": self.get_number_of_citations,
-                "raw_func": self._get_raw_citation_data,
-            })
-        self._tables['Event'].update(
-            {
-                "handle_func": self.get_event_from_handle, 
-                "gramps_id_func": self.get_event_from_gramps_id,
-                "class_func": Event,
-                "cursor_func": self.get_event_cursor,
-                "handles_func": self.get_event_handles,
-                "add_func": self.add_event,
-                "commit_func": self.commit_event,
-                "iter_func": self.iter_events,
-                "ids_func": self.get_event_gramps_ids,
-                "has_handle_func": self.has_handle_for_event,
-                "has_gramps_id_func": self.has_gramps_id_for_event,
-                "count": self.get_number_of_events,
-                "raw_func": self._get_raw_event_data,
-            })
-        self._tables['Media'].update(
-            {
-                "handle_func": self.get_object_from_handle, 
-                "gramps_id_func": self.get_object_from_gramps_id,
-                "class_func": MediaObject,
-                "cursor_func": self.get_media_cursor,
-                "handles_func": self.get_media_object_handles,
-                "add_func": self.add_object,
-                "commit_func": self.commit_media_object,
-                "iter_func": self.iter_media_objects,
-                "ids_func": self.get_media_gramps_ids,
-                "has_handle_func": self.has_handle_for_media,
-                "has_gramps_id_func": self.has_gramps_id_for_media,
-                "count": self.get_number_of_media_objects,
-                "raw_func": self._get_raw_media_data,
-            })
-        self._tables['Place'].update(
-            {
-                "handle_func": self.get_place_from_handle, 
-                "gramps_id_func": self.get_place_from_gramps_id,
-                "class_func": Place,
-                "cursor_func": self.get_place_cursor,
-                "handles_func": self.get_place_handles,
-                "add_func": self.add_place,
-                "commit_func": self.commit_place,
-                "iter_func": self.iter_places,
-                "ids_func": self.get_place_gramps_ids,
-                "has_handle_func": self.has_handle_for_place,
-                "has_gramps_id_func": self.has_gramps_id_for_place,
-                "count": self.get_number_of_places,
-                "raw_func": self._get_raw_place_data,
-            })
-        self._tables['Repository'].update(
-            {
-                "handle_func": self.get_repository_from_handle, 
-                "gramps_id_func": self.get_repository_from_gramps_id,
-                "class_func": Repository,
-                "cursor_func": self.get_repository_cursor,
-                "handles_func": self.get_repository_handles,
-                "add_func": self.add_repository,
-                "commit_func": self.commit_repository,
-                "iter_func": self.iter_repositories,
-                "ids_func": self.get_repository_gramps_ids,
-                "has_handle_func": self.has_handle_for_repository,
-                "has_gramps_id_func": self.has_gramps_id_for_repository,
-                "count": self.get_number_of_repositories,
-                "raw_func": self._get_raw_repository_data,
-            })
-        self._tables['Note'].update(
-            {
-                "handle_func": self.get_note_from_handle, 
-                "gramps_id_func": self.get_note_from_gramps_id,
-                "class_func": Note,
-                "cursor_func": self.get_note_cursor,
-                "handles_func": self.get_note_handles,
-                "add_func": self.add_note,
-                "commit_func": self.commit_note,
-                "iter_func": self.iter_notes,
-                "ids_func": self.get_note_gramps_ids,
-                "has_handle_func": self.has_handle_for_note,
-                "has_gramps_id_func": self.has_gramps_id_for_note,
-                "count": self.get_number_of_notes,
-                "raw_func": self._get_raw_note_data,
-            })
-        self._tables['Tag'].update(
-            {
-                "handle_func": self.get_tag_from_handle, 
-                "gramps_id_func": None,
-                "class_func": Tag,
-                "cursor_func": self.get_tag_cursor,
-                "handles_func": self.get_tag_handles,
-                "add_func": self.add_tag,
-                "commit_func": self.commit_tag,
-                "has_handle_func": self.has_handle_for_tag,
-                "iter_func": self.iter_tags,
-                "count": self.get_number_of_tags,
-                "raw_func": self._get_raw_tag_data,
-            })
-        self.set_save_path(directory)
-        # skip GEDCOM cross-ref check for now:
-        self.set_feature("skip-check-xref", True)
-        self.set_feature("skip-import-additions", True)
-        self.readonly = False
-        self.db_is_open = True
-        self.name_formats = []
-        # Bookmarks:
-        self.bookmarks = Bookmarks()
-        self.family_bookmarks = Bookmarks()
-        self.event_bookmarks = Bookmarks()
-        self.place_bookmarks = Bookmarks()
-        self.citation_bookmarks = Bookmarks()
-        self.source_bookmarks = Bookmarks()
-        self.repo_bookmarks = Bookmarks()
-        self.media_bookmarks = Bookmarks()
-        self.note_bookmarks = Bookmarks()
-        self.set_person_id_prefix('I%04d')
-        self.set_object_id_prefix('O%04d')
-        self.set_family_id_prefix('F%04d')
-        self.set_citation_id_prefix('C%04d')
-        self.set_source_id_prefix('S%04d')
-        self.set_place_id_prefix('P%04d')
-        self.set_event_id_prefix('E%04d')
-        self.set_repository_id_prefix('R%04d')
-        self.set_note_id_prefix('N%04d')
-        # ----------------------------------
-        self.undodb = None
-        self.id_trans  = DBAPITxn("ID Transaction", self)
-        self.fid_trans = DBAPITxn("FID Transaction", self)
-        self.pid_trans = DBAPITxn("PID Transaction", self)
-        self.cid_trans = DBAPITxn("CID Transaction", self)
-        self.sid_trans = DBAPITxn("SID Transaction", self)
-        self.oid_trans = DBAPITxn("OID Transaction", self)
-        self.rid_trans = DBAPITxn("RID Transaction", self)
-        self.nid_trans = DBAPITxn("NID Transaction", self)
-        self.eid_trans = DBAPITxn("EID Transaction", self)
-        self.cmap_index = 0
-        self.smap_index = 0
-        self.emap_index = 0
-        self.pmap_index = 0
-        self.fmap_index = 0
-        self.lmap_index = 0
-        self.omap_index = 0
-        self.rmap_index = 0
-        self.nmap_index = 0
-        self.env = Environment(self)
-        self.person_map = Map(Table(self, "Person"))
-        self.person_id_map = Map(Table(self, "Person"),
-                                 keys_func="ids_func",
-                                 contains_func="has_gramps_id_func")
-        self.family_map = Map(Table(self, "Family"))
-        self.family_id_map = Map(Table(self, "Family"),
-                                 keys_func="ids_func",
-                                 contains_func="has_gramps_id_func")
-        self.place_map  = Map(Table(self, "Place"))
-        self.place_id_map = Map(Table(self, "Place"),
-                                 keys_func="ids_func",
-                                 contains_func="has_gramps_id_func")
-        self.citation_map = Map(Table(self, "Citation"))
-        self.citation_id_map = Map(Table(self, "Citation"),
-                                 keys_func="ids_func",
-                                 contains_func="has_gramps_id_func")
-        self.source_map = Map(Table(self, "Source"))
-        self.source_id_map = Map(Table(self, "Source"),
-                                 keys_func="ids_func",
-                                contains_func="has_gramps_id_func")
-        self.repository_map  = Map(Table(self, "Repository"))
-        self.repository_id_map = Map(Table(self, "Repository"),
-                                 keys_func="ids_func",
-                                 contains_func="has_gramps_id_func")
-        self.note_map = Map(Table(self, "Note"))
-        self.note_id_map = Map(Table(self, "Note"),
-                                 keys_func="ids_func",
-                                 contains_func="has_gramps_id_func")
-        self.media_map  = Map(Table(self, "Media"))
-        self.media_id_map = Map(Table(self, "Media"),
-                                 keys_func="ids_func",
-                                 contains_func="has_gramps_id_func")
-        self.event_map  = Map(Table(self, "Event"))
-        self.event_id_map = Map(Table(self, "Event"), 
-                                 keys_func="ids_func",
-                                 contains_func="has_gramps_id_func")
-        self.tag_map  = Map(Table(self, "Tag"))
-        self.metadata   = Map(Table(self, "Metadata", funcs={"cursor_func": lambda: MetaCursor()}))
-        self.undo_callback = None
-        self.redo_callback = None
-        self.undo_history_callback = None
-        self.modified   = 0
-        self.txn = DBAPITxn("DBAPI Transaction", self)
-        self.transaction = None
-        self.abort_possible = False
-        self._bm_changes = 0
-        self.has_changed = False
-        self.genderStats = GenderStats() # can pass in loaded stats as dict
-        self.owner = Researcher()
-        if directory:
-            self.load(directory)
-
-    def version_supported(self):
-        """Return True when the file has a supported version."""
-        return True
-
-    def get_table_names(self):
-        """Return a list of valid table names."""
-        return list(self._tables.keys())
-
-    def get_table_metadata(self, table_name):
-        """Return the metadata for a valid table name."""
-        if table_name in self._tables:
-            return self._tables[table_name]
-        return None
-
-    def transaction_commit(self, txn):
-        self.dbapi.commit()
-
-    def transaction_abort(self, txn):
-        self.dbapi.rollback()
-
-    @staticmethod
-    def _validated_id_prefix(val, default):
-        if isinstance(val, str) and val:
-            try:
-                str_ = val % 1
-            except TypeError:           # missing conversion specifier
-                prefix_var = val + "%d"
-            except ValueError:          # incomplete format
-                prefix_var = default+"%04d"
-            else:
-                prefix_var = val        # OK as given
-        else:
-            prefix_var = default+"%04d" # not a string or empty string
-        return prefix_var
-
-    @staticmethod
-    def __id2user_format(id_pattern):
-        """
-        Return a method that accepts a Gramps ID and adjusts it to the users
-        format.
-        """
-        pattern_match = re.match(r"(.*)%[0 ](\d+)[diu]$", id_pattern)
-        if pattern_match:
-            str_prefix = pattern_match.group(1)
-            nr_width = int(pattern_match.group(2))
-            def closure_func(gramps_id):
-                if gramps_id and gramps_id.startswith(str_prefix):
-                    id_number = gramps_id[len(str_prefix):]
-                    if id_number.isdigit():
-                        id_value = int(id_number, 10)
-                        #if len(str(id_value)) > nr_width:
-                        #    # The ID to be imported is too large to fit in the
-                        #    # users format. For now just create a new ID,
-                        #    # because that is also what happens with IDs that
-                        #    # are identical to IDs already in the database. If
-                        #    # the problem of colliding import and already
-                        #    # present IDs is solved the code here also needs
-                        #    # some solution.
-                        #    gramps_id = id_pattern % 1
-                        #else:
-                        gramps_id = id_pattern % id_value
-                return gramps_id
-        else:
-            def closure_func(gramps_id):
-                return gramps_id
-        return closure_func
-
-    def set_person_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Person ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as I%d or I%04d.
-        """
-        self.person_prefix = self._validated_id_prefix(val, "I")
-        self.id2user_format = self.__id2user_format(self.person_prefix)
-
-    def set_citation_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Citation ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as C%d or C%04d.
-        """
-        self.citation_prefix = self._validated_id_prefix(val, "C")
-        self.cid2user_format = self.__id2user_format(self.citation_prefix)
-            
-    def set_source_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Source ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as S%d or S%04d.
-        """
-        self.source_prefix = self._validated_id_prefix(val, "S")
-        self.sid2user_format = self.__id2user_format(self.source_prefix)
-            
-    def set_object_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS MediaObject ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as O%d or O%04d.
-        """
-        self.mediaobject_prefix = self._validated_id_prefix(val, "O")
-        self.oid2user_format = self.__id2user_format(self.mediaobject_prefix)
-
-    def set_place_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Place ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as P%d or P%04d.
-        """
-        self.place_prefix = self._validated_id_prefix(val, "P")
-        self.pid2user_format = self.__id2user_format(self.place_prefix)
-
-    def set_family_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Family ID values. The string is
-        expected to be in the form of a simple text string, or in a format
-        that contains a C/Python style format string using %d, such as F%d
-        or F%04d.
-        """
-        self.family_prefix = self._validated_id_prefix(val, "F")
-        self.fid2user_format = self.__id2user_format(self.family_prefix)
-
-    def set_event_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Event ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as E%d or E%04d.
-        """
-        self.event_prefix = self._validated_id_prefix(val, "E")
-        self.eid2user_format = self.__id2user_format(self.event_prefix)
-
-    def set_repository_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Repository ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as R%d or R%04d.
-        """
-        self.repository_prefix = self._validated_id_prefix(val, "R")
-        self.rid2user_format = self.__id2user_format(self.repository_prefix)
-
-    def set_note_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Note ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as N%d or N%04d.
-        """
-        self.note_prefix = self._validated_id_prefix(val, "N")
-        self.nid2user_format = self.__id2user_format(self.note_prefix)
-
-    def __find_next_gramps_id(self, prefix, map_index, trans):
-        """
-        Helper function for find_next_<object>_gramps_id methods
-        """
-        index = prefix % map_index
-        while trans.get(str(index), txn=self.txn) is not None:
-            map_index += 1
-            index = prefix % map_index
-        map_index += 1
-        return (map_index, index)
-        
-    def find_next_person_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Person object based off the 
-        person ID prefix.
-        """
-        self.pmap_index, gid = self.__find_next_gramps_id(self.person_prefix,
-                                          self.pmap_index, self.id_trans)
-        return gid
-
-    def find_next_place_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Place object based off the 
-        place ID prefix.
-        """
-        self.lmap_index, gid = self.__find_next_gramps_id(self.place_prefix,
-                                          self.lmap_index, self.pid_trans)
-        return gid
-
-    def find_next_event_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Event object based off the 
-        event ID prefix.
-        """
-        self.emap_index, gid = self.__find_next_gramps_id(self.event_prefix,
-                                          self.emap_index, self.eid_trans)
-        return gid
-
-    def find_next_object_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a MediaObject object based
-        off the media object ID prefix.
-        """
-        self.omap_index, gid = self.__find_next_gramps_id(self.mediaobject_prefix,
-                                          self.omap_index, self.oid_trans)
-        return gid
-
-    def find_next_citation_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Citation object based off the 
-        citation ID prefix.
-        """
-        self.cmap_index, gid = self.__find_next_gramps_id(self.citation_prefix,
-                                          self.cmap_index, self.cid_trans)
-        return gid
-
-    def find_next_source_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Source object based off the 
-        source ID prefix.
-        """
-        self.smap_index, gid = self.__find_next_gramps_id(self.source_prefix,
-                                          self.smap_index, self.sid_trans)
-        return gid
-
-    def find_next_family_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Family object based off the 
-        family ID prefix.
-        """
-        self.fmap_index, gid = self.__find_next_gramps_id(self.family_prefix,
-                                          self.fmap_index, self.fid_trans)
-        return gid
-
-    def find_next_repository_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Respository object based 
-        off the repository ID prefix.
-        """
-        self.rmap_index, gid = self.__find_next_gramps_id(self.repository_prefix,
-                                          self.rmap_index, self.rid_trans)
-        return gid
-
-    def find_next_note_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Note object based off the 
-        note ID prefix.
-        """
-        self.nmap_index, gid = self.__find_next_gramps_id(self.note_prefix,
-                                          self.nmap_index, self.nid_trans)
-        return gid
-
-    def get_mediapath(self):
-        return self.get_metadata("media-path", "")
-
-    def set_mediapath(self, mediapath):
-        return self.set_metadata("media-path", mediapath)
-
-    def get_metadata(self, key, default=[]):
-        """
-        Get an item from the database.
-        """
-        cur = self.dbapi.execute("SELECT * FROM metadata WHERE setting = ?;", [key])
-        row = cur.fetchone()
-        if row:
-            return pickle.loads(row["value"])
-        elif default == []:
-            return []
-        else:
-            return default
-
-    def set_metadata(self, key, value):
-        """
-        key: string
-        value: item, will be serialized here
-        """
-        cur = self.dbapi.execute("SELECT * FROM metadata WHERE setting = ?;", [key])
-        row = cur.fetchone()
-        if row:
-            cur = self.dbapi.execute("UPDATE metadata SET value = ? WHERE setting = ?;", 
-                                     [pickle.dumps(value), key])
-        else:
-            cur = self.dbapi.execute("INSERT INTO metadata (setting, value) VALUES (?, ?);", 
-                                     [key, pickle.dumps(value)])
-        self.dbapi.commit()
-
-    def set_default_person_handle(self, handle):
-        self.set_metadata("default-person-handle", handle)
-        self.emit('home-person-changed')
-
-    def get_name_group_keys(self):
-        cur = self.dbapi.execute("SELECT name FROM name_group ORDER BY name;")
-        rows = cur.fetchall()
-        return [row[0] for row in rows]
-
-    def get_name_group_mapping(self, key):
-        cur = self.dbapi.execute("SELECT grouping FROM name_group WHERE name = ?;", 
-                                 [key])
-        row = cur.fetchone()
-        if row:
-            return row[0]
-
-    def get_person_handles(self, sort_handles=False):
-        if sort_handles:
-            cur = self.dbapi.execute("SELECT handle FROM person ORDER BY order_by;")
-        else:
-            cur = self.dbapi.execute("SELECT handle FROM person;")
-        rows = cur.fetchall()
-        return [row[0] for row in rows]
-
-    def get_family_handles(self):
-        cur = self.dbapi.execute("SELECT handle FROM family;")
-        rows = cur.fetchall()
-        return [row[0] for row in rows]
-
-    def get_event_handles(self):
-        cur = self.dbapi.execute("SELECT handle FROM event;")
-        rows = cur.fetchall()
-        return [row[0] for row in rows]
-
-    def get_citation_handles(self, sort_handles=False):
-        if sort_handles:
-            cur = self.dbapi.execute("SELECT handle FROM citation ORDER BY order_by;")
-        else:
-            cur = self.dbapi.execute("SELECT handle FROM citation;")
-        rows = cur.fetchall()
-        return [row[0] for row in rows]
-
-    def get_source_handles(self, sort_handles=False):
-        if sort_handles:
-            cur = self.dbapi.execute("SELECT handle FROM source ORDER BY order_by;")
-        else:
-            cur = self.dbapi.execute("SELECT handle from source;")
-        rows = cur.fetchall()
-        return [row[0] for row in rows]
-
-    def get_place_handles(self, sort_handles=False):
-        if sort_handles:
-            cur = self.dbapi.execute("SELECT handle FROM place ORDER BY order_by;")
-        else:
-            cur = self.dbapi.execute("SELECT handle FROM place;")
-        rows = cur.fetchall()
-        return [row[0] for row in rows]
-
-    def get_repository_handles(self):
-        cur = self.dbapi.execute("SELECT handle FROM repository;")
-        rows = cur.fetchall()
-        return [row[0] for row in rows]
-
-    def get_media_object_handles(self, sort_handles=False):
-        if sort_handles:
-            cur = self.dbapi.execute("SELECT handle FROM media ORDER BY order_by;")
-        else:
-            cur = self.dbapi.execute("SELECT handle FROM media;")
-        rows = cur.fetchall()
-        return [row[0] for row in rows]
-
-    def get_note_handles(self):
-        cur = self.dbapi.execute("SELECT handle FROM note;")
-        rows = cur.fetchall()
-        return [row[0] for row in rows]
-
-    def get_tag_handles(self, sort_handles=False):
-        if sort_handles:
-            cur = self.dbapi.execute("SELECT handle FROM tag ORDER BY order_by;")
-        else:
-            cur = self.dbapi.execute("SELECT handle FROM tag;")
-        rows = cur.fetchall()
-        return [row[0] for row in rows]
-
-    def get_event_from_handle(self, handle):
-        if isinstance(handle, bytes):
-            handle = str(handle, "utf-8")
-        event = None
-        if handle in self.event_map:
-            event = Event.create(self._get_raw_event_data(handle))
-        return event
-
-    def get_family_from_handle(self, handle): 
-        if isinstance(handle, bytes):
-            handle = str(handle, "utf-8")
-        family = None
-        if handle in self.family_map:
-            family = Family.create(self._get_raw_family_data(handle))
-        return family
-
-    def get_repository_from_handle(self, handle):
-        if isinstance(handle, bytes):
-            handle = str(handle, "utf-8")
-        repository = None
-        if handle in self.repository_map:
-            repository = Repository.create(self._get_raw_repository_data(handle))
-        return repository
-
-    def get_person_from_handle(self, handle):
-        if isinstance(handle, bytes):
-            handle = str(handle, "utf-8")
-        person = None
-        if handle in self.person_map:
-            person = Person.create(self._get_raw_person_data(handle))
-        return person
-
-    def get_place_from_handle(self, handle):
-        if isinstance(handle, bytes):
-            handle = str(handle, "utf-8")
-        place = None
-        if handle in self.place_map:
-            place = Place.create(self._get_raw_place_data(handle))
-        return place
-
-    def get_citation_from_handle(self, handle):
-        if isinstance(handle, bytes):
-            handle = str(handle, "utf-8")
-        citation = None
-        if handle in self.citation_map:
-            citation = Citation.create(self._get_raw_citation_data(handle))
-        return citation
-
-    def get_source_from_handle(self, handle):
-        if isinstance(handle, bytes):
-            handle = str(handle, "utf-8")
-        source = None
-        if handle in self.source_map:
-            source = Source.create(self._get_raw_source_data(handle))
-        return source
-
-    def get_note_from_handle(self, handle):
-        if isinstance(handle, bytes):
-            handle = str(handle, "utf-8")
-        note = None
-        if handle in self.note_map:
-            note = Note.create(self._get_raw_note_data(handle))
-        return note
-
-    def get_object_from_handle(self, handle):
-        if isinstance(handle, bytes):
-            handle = str(handle, "utf-8")
-        media = None
-        if handle in self.media_map:
-            media = MediaObject.create(self._get_raw_media_data(handle))
-        return media
-
-    def get_tag_from_handle(self, handle):
-        if isinstance(handle, bytes):
-            handle = str(handle, "utf-8")
-        tag = None
-        if handle in self.tag_map:
-            tag = Tag.create(self._get_raw_tag_data(handle))
-        return tag
-
-    def get_default_person(self):
-        handle = self.get_default_handle()
-        if handle:
-            return self.get_person_from_handle(handle)
-        else:
-            return None
-
-    def iter_people(self):
-        return (Person.create(data[1]) for data in self.get_person_cursor())
-
-    def iter_person_handles(self):
-        return (data[0] for data in self.get_person_cursor())
-
-    def iter_families(self):
-        return (Family.create(data[1]) for data in self.get_family_cursor())
-
-    def iter_family_handles(self):
-        return (handle for handle in self.family_map.keys())
-
-    def get_tag_from_name(self, name):
-        cur = self.dbapi.execute("""select handle from tag where order_by = ?;""",
-                                 [self._order_by_tag_key(name)])
-        row = cur.fetchone()
-        if row:
-            self.get_tag_from_handle(row[0])
-        return None
-
-    def get_person_from_gramps_id(self, gramps_id):
-        if gramps_id in self.person_id_map:
-            return Person.create(self.person_id_map[gramps_id])
-        return None
-
-    def get_family_from_gramps_id(self, gramps_id):
-        if gramps_id in self.family_id_map:
-            return Family.create(self.family_id_map[gramps_id])
-        return None
-
-    def get_citation_from_gramps_id(self, gramps_id):
-        if gramps_id in self.citation_id_map:
-            return Citation.create(self.citation_id_map[gramps_id])
-        return None
-
-    def get_source_from_gramps_id(self, gramps_id):
-        if gramps_id in self.source_id_map:
-            return Source.create(self.source_id_map[gramps_id])
-        return None
-
-    def get_event_from_gramps_id(self, gramps_id):
-        if gramps_id in self.event_id_map:
-            return Event.create(self.event_id_map[gramps_id])
-        return None
-
-    def get_media_from_gramps_id(self, gramps_id):
-        if gramps_id in self.media_id_map:
-            return MediaObject.create(self.media_id_map[gramps_id])
-        return None
-
-    def get_place_from_gramps_id(self, gramps_id):
-        if gramps_id in self.place_id_map:
-            return Place.create(self.place_id_map[gramps_id])
-        return None
-
-    def get_repository_from_gramps_id(self, gramps_id):
-        if gramps_id in self.repository_id_map:
-            return Repository.create(self.repository_id_map[gramps_id])
-        return None
-
-    def get_note_from_gramps_id(self, gramps_id):
-        if gramps_id in self.note_id_map:
-            return Note.create(self.note_id_map[gramps_id])
-        return None
-
-    def get_number_of_people(self):
-        cur = self.dbapi.execute("SELECT count(handle) FROM person;")
-        row = cur.fetchone()
-        return row[0]
-
-    def get_number_of_events(self):
-        cur = self.dbapi.execute("SELECT count(handle) FROM event;")
-        row = cur.fetchone()
-        return row[0]
-
-    def get_number_of_places(self):
-        cur = self.dbapi.execute("SELECT count(handle) FROM place;")
-        row = cur.fetchone()
-        return row[0]
-
-    def get_number_of_tags(self):
-        cur = self.dbapi.execute("SELECT count(handle) FROM tag;")
-        row = cur.fetchone()
-        return row[0]
-
-    def get_number_of_families(self):
-        cur = self.dbapi.execute("SELECT count(handle) FROM family;")
-        row = cur.fetchone()
-        return row[0]
-
-    def get_number_of_notes(self):
-        cur = self.dbapi.execute("SELECT count(handle) FROM note;")
-        row = cur.fetchone()
-        return row[0]
-
-    def get_number_of_citations(self):
-        cur = self.dbapi.execute("SELECT count(handle) FROM citation;")
-        row = cur.fetchone()
-        return row[0]
-
-    def get_number_of_sources(self):
-        cur = self.dbapi.execute("SELECT count(handle) FROM source;")
-        row = cur.fetchone()
-        return row[0]
-
-    def get_number_of_media_objects(self):
-        cur = self.dbapi.execute("SELECT count(handle) FROM media;")
-        row = cur.fetchone()
-        return row[0]
-
-    def get_number_of_repositories(self):
-        cur = self.dbapi.execute("SELECT count(handle) FROM repository;")
-        row = cur.fetchone()
-        return row[0]
-
-    def get_place_cursor(self):
-        return Cursor(self.place_map)
-
-    def get_person_cursor(self):
-        return Cursor(self.person_map)
-
-    def get_family_cursor(self):
-        return Cursor(self.family_map)
-
-    def get_event_cursor(self):
-        return Cursor(self.event_map)
-
-    def get_note_cursor(self):
-        return Cursor(self.note_map)
-
-    def get_tag_cursor(self):
-        return Cursor(self.tag_map)
-
-    def get_repository_cursor(self):
-        return Cursor(self.repository_map)
-
-    def get_media_cursor(self):
-        return Cursor(self.media_map)
-
-    def get_citation_cursor(self):
-        return Cursor(self.citation_map)
-
-    def get_source_cursor(self):
-        return Cursor(self.source_map)
-
-    def has_gramps_id(self, obj_key, gramps_id):
-        key2table = {
-            PERSON_KEY:     self.person_id_map, 
-            FAMILY_KEY:     self.family_id_map, 
-            SOURCE_KEY:     self.source_id_map, 
-            CITATION_KEY:   self.citation_id_map, 
-            EVENT_KEY:      self.event_id_map, 
-            MEDIA_KEY:      self.media_id_map, 
-            PLACE_KEY:      self.place_id_map, 
-            REPOSITORY_KEY: self.repository_id_map, 
-            NOTE_KEY:       self.note_id_map, 
-            }
-        return gramps_id in key2table[obj_key]
-
-    def has_person_handle(self, handle):
-        return handle in self.person_map
-
-    def has_family_handle(self, handle):
-        return handle in self.family_map
-
-    def has_citation_handle(self, handle):
-        return handle in self.citation_map
-
-    def has_source_handle(self, handle):
-        return handle in self.source_map
-
-    def has_repository_handle(self, handle):
-        return handle in self.repository_map
-
-    def has_note_handle(self, handle):
-        return handle in self.note_map
-
-    def has_place_handle(self, handle):
-        return handle in self.place_map
-
-    def has_event_handle(self, handle):
-        return handle in self.event_map
-
-    def has_tag_handle(self, handle):
-        return handle in self.tag_map
-
-    def has_object_handle(self, handle):
-        return handle in self.media_map
-
-    def has_name_group_key(self, key):
-        cur = self.dbapi.execute("SELECT grouping FROM name_group WHERE name = ?;", 
-                                 [key])
-        row = cur.fetchone()
-        return True if row else False
-
-    def set_name_group_mapping(self, name, grouping):
-        sname = name.encode("utf-8")
-        cur = self.dbapi.execute("SELECT * FROM name_group WHERE name = ?;", 
-                                 [sname])
-        row = cur.fetchone()
-        if row:
-            cur = self.dbapi.execute("DELETE FROM name_group WHERE name = ?;", 
-                                     [sname])
-        cur = self.dbapi.execute("INSERT INTO name_group (name, grouping) VALUES(?, ?);",
-                                 [sname, grouping])
-        self.dbapi.commit()
-
-    def get_raw_person_data(self, handle):
-        if handle in self.person_map:
-            return self.person_map[handle]
-        return None
-
-    def get_raw_family_data(self, handle):
-        if handle in self.family_map:
-            return self.family_map[handle]
-        return None
-
-    def get_raw_citation_data(self, handle):
-        if handle in self.citation_map:
-            return self.citation_map[handle]
-        return None
-
-    def get_raw_source_data(self, handle):
-        if handle in self.source_map:
-            return self.source_map[handle]
-        return None
-
-    def get_raw_repository_data(self, handle):
-        if handle in self.repository_map:
-            return self.repository_map[handle]
-        return None
-
-    def get_raw_note_data(self, handle):
-        if handle in self.note_map:
-            return self.note_map[handle]
-        return None
-
-    def get_raw_place_data(self, handle):
-        if handle in self.place_map:
-            return self.place_map[handle]
-        return None
-
-    def get_raw_object_data(self, handle):
-        if handle in self.media_map:
-            return self.media_map[handle]
-        return None
-
-    def get_raw_tag_data(self, handle):
-        if handle in self.tag_map:
-            return self.tag_map[handle]
-        return None
-
-    def get_raw_event_data(self, handle):
-        if handle in self.event_map:
-            return self.event_map[handle]
-        return None
-
-    def add_person(self, person, trans, set_gid=True):
-        if not person.handle:
-            person.handle = create_id()
-        if not person.gramps_id and set_gid:
-            person.gramps_id = self.find_next_person_gramps_id()
-        self.commit_person(person, trans)
-        return person.handle
-
-    def add_family(self, family, trans, set_gid=True):
-        if not family.handle:
-            family.handle = create_id()
-        if not family.gramps_id and set_gid:
-            family.gramps_id = self.find_next_family_gramps_id()
-        self.commit_family(family, trans)
-        return family.handle
-
-    def add_citation(self, citation, trans, set_gid=True):
-        if not citation.handle:
-            citation.handle = create_id()
-        if not citation.gramps_id and set_gid:
-            citation.gramps_id = self.find_next_citation_gramps_id()
-        self.commit_citation(citation, trans)
-        return citation.handle
-
-    def add_source(self, source, trans, set_gid=True):
-        if not source.handle:
-            source.handle = create_id()
-        if not source.gramps_id and set_gid:
-            source.gramps_id = self.find_next_source_gramps_id()
-        self.commit_source(source, trans)
-        return source.handle
-
-    def add_repository(self, repository, trans, set_gid=True):
-        if not repository.handle:
-            repository.handle = create_id()
-        if not repository.gramps_id and set_gid:
-            repository.gramps_id = self.find_next_repository_gramps_id()
-        self.commit_repository(repository, trans)
-        return repository.handle
-
-    def add_note(self, note, trans, set_gid=True):
-        if not note.handle:
-            note.handle = create_id()
-        if not note.gramps_id and set_gid:
-            note.gramps_id = self.find_next_note_gramps_id()
-        self.commit_note(note, trans)
-        return note.handle
-
-    def add_place(self, place, trans, set_gid=True):
-        if not place.handle:
-            place.handle = create_id()
-        if not place.gramps_id and set_gid:
-            place.gramps_id = self.find_next_place_gramps_id()
-        self.commit_place(place, trans)
-        return place.handle
-
-    def add_event(self, event, trans, set_gid=True):
-        if not event.handle:
-            event.handle = create_id()
-        if not event.gramps_id and set_gid:
-            event.gramps_id = self.find_next_event_gramps_id()
-        self.commit_event(event, trans)
-        return event.handle
-
-    def add_tag(self, tag, trans):
-        if not tag.handle:
-            tag.handle = create_id()
-        self.commit_tag(tag, trans)
-        return tag.handle
-
-    def add_object(self, obj, transaction, set_gid=True):
-        """
-        Add a MediaObject to the database, assigning internal IDs if they have
-        not already been defined.
-        
-        If not set_gid, then gramps_id is not set.
-        """
-        if not obj.handle:
-            obj.handle = create_id()
-        if not obj.gramps_id and set_gid:
-            obj.gramps_id = self.find_next_object_gramps_id()
-        self.commit_media_object(obj, transaction)
-        return obj.handle
-
-    def commit_person(self, person, trans, change_time=None):
-        emit = None
-        if person.handle in self.person_map:
-            emit = "person-update"
-            old_person = self.get_person_from_handle(person.handle)
-            # Update gender statistics if necessary
-            if (old_person.gender != person.gender or
-                old_person.primary_name.first_name !=
-                  person.primary_name.first_name):
-
-                self.genderStats.uncount_person(old_person)
-                self.genderStats.count_person(person)
-            # Update surname list if necessary
-            if (self._order_by_person_key(person) != 
-                self._order_by_person_key(old_person)):
-                self.remove_from_surname_list(old_person)
-                self.add_to_surname_list(person, trans.batch)
-            # update the person:
-            self.dbapi.execute("""UPDATE person SET gramps_id = ?, 
-                                                    order_by = ?,
-                                                    blob = ? 
-                                                WHERE handle = ?;""",
-                               [person.gramps_id, 
-                                self._order_by_person_key(person),
-                                pickle.dumps(person.serialize()),
-                                person.handle])
-        else:
-            emit = "person-add"
-            self.genderStats.count_person(person)
-            self.add_to_surname_list(person, trans.batch)
-            # Insert the person:
-            self.dbapi.execute("""INSERT INTO person (handle, order_by, gramps_id, blob)
-                            VALUES(?, ?, ?, ?);""", 
-                               [person.handle, 
-                                self._order_by_person_key(person),
-                                person.gramps_id, 
-                                pickle.dumps(person.serialize())])
-        if not trans.batch:
-            self.update_backlinks(person)
-            self.dbapi.commit()
-        # Other misc update tasks:
-        self.individual_attributes.update(
-            [str(attr.type) for attr in person.attribute_list
-             if attr.type.is_custom() and str(attr.type)])
-
-        self.event_role_names.update([str(eref.role)
-                                      for eref in person.event_ref_list
-                                      if eref.role.is_custom()])
-
-        self.name_types.update([str(name.type)
-                                for name in ([person.primary_name]
-                                             + person.alternate_names)
-                                if name.type.is_custom()])
-        all_surn = []  # new list we will use for storage
-        all_surn += person.primary_name.get_surname_list() 
-        for asurname in person.alternate_names:
-            all_surn += asurname.get_surname_list()
-        self.origin_types.update([str(surn.origintype) for surn in all_surn
-                                if surn.origintype.is_custom()])
-        all_surn = None
-        self.url_types.update([str(url.type) for url in person.urls
-                               if url.type.is_custom()])
-        attr_list = []
-        for mref in person.media_list:
-            attr_list += [str(attr.type) for attr in mref.attribute_list
-                          if attr.type.is_custom() and str(attr.type)]
-        self.media_attributes.update(attr_list)
-        # Emit after added:
-        if emit:
-            self.emit(emit, ([person.handle],))
-        self.has_changed = True
-
-    def add_to_surname_list(self, person, batch_transaction):
-        """
-        Add surname to surname list
-        """
-        if batch_transaction:
-            return
-        # TODO: check to see if this is correct
-        name = None
-        primary_name = person.get_primary_name()
-        if primary_name:
-            surname_list = primary_name.get_surname_list()
-            if len(surname_list) > 0:
-                name = surname_list[0].surname
-        if name is None:
-            return
-        i = bisect.bisect(self.surname_list, name)
-        if 0 < i <= len(self.surname_list):
-            if self.surname_list[i-1] != name:
-                self.surname_list.insert(i, name)
-        else:
-            self.surname_list.insert(i, name)
-
-    def remove_from_surname_list(self, person):
-        """
-        Check whether there are persons with the same surname left in
-        the database. 
-        
-        If not then we need to remove the name from the list.
-        The function must be overridden in the derived class.
-        """
-        name = None
-        primary_name = person.get_primary_name()
-        if primary_name:
-            surname_list = primary_name.get_surname_list()
-            if len(surname_list) > 0:
-                name = surname_list[0].surname
-        if name is None:
-            return
-        if name in self.surname_list:
-            self.surname_list.remove(name)
-
-    def commit_family(self, family, trans, change_time=None):
-        emit = None
-        if family.handle in self.family_map:
-            emit = "family-update"
-            self.dbapi.execute("""UPDATE family SET gramps_id = ?, 
-                                                    blob = ? 
-                                                WHERE handle = ?;""",
-                               [family.gramps_id, 
-                                pickle.dumps(family.serialize()),
-                                family.handle])
-        else:
-            emit = "family-add"
-            self.dbapi.execute("""INSERT INTO family (handle, gramps_id, blob)
-                    VALUES(?, ?, ?);""", 
-                               [family.handle, family.gramps_id, 
-                                pickle.dumps(family.serialize())])
-        if not trans.batch:
-            self.update_backlinks(family)
-            self.dbapi.commit()
-        # Misc updates:
-        self.family_attributes.update(
-            [str(attr.type) for attr in family.attribute_list
-             if attr.type.is_custom() and str(attr.type)])
-
-        rel_list = []
-        for ref in family.child_ref_list:
-            if ref.frel.is_custom():
-                rel_list.append(str(ref.frel))
-            if ref.mrel.is_custom():
-                rel_list.append(str(ref.mrel))
-        self.child_ref_types.update(rel_list)
-
-        self.event_role_names.update(
-            [str(eref.role) for eref in family.event_ref_list
-             if eref.role.is_custom()])
-
-        if family.type.is_custom():
-            self.family_rel_types.add(str(family.type))
-
-        attr_list = []
-        for mref in family.media_list:
-            attr_list += [str(attr.type) for attr in mref.attribute_list
-                          if attr.type.is_custom() and str(attr.type)]
-        self.media_attributes.update(attr_list)
-        # Emit after added:
-        if emit:
-            self.emit(emit, ([family.handle],))
-        self.has_changed = True
-
-    def commit_citation(self, citation, trans, change_time=None):
-        emit = None
-        if citation.handle in self.citation_map:
-            emit = "citation-update"
-            self.dbapi.execute("""UPDATE citation SET gramps_id = ?, 
-                                                      order_by = ?,
-                                                      blob = ? 
-                                                WHERE handle = ?;""",
-                               [citation.gramps_id, 
-                                self._order_by_citation_key(citation),
-                                pickle.dumps(citation.serialize()),
-                                citation.handle])
-        else:
-            emit = "citation-add"
-            self.dbapi.execute("""INSERT INTO citation (handle, order_by, gramps_id, blob)
-                     VALUES(?, ?, ?, ?);""", 
-                       [citation.handle, 
-                        self._order_by_citation_key(citation),
-                        citation.gramps_id, 
-                        pickle.dumps(citation.serialize())])
-        if not trans.batch:
-            self.update_backlinks(citation)
-            self.dbapi.commit()
-        # Misc updates:
-        attr_list = []
-        for mref in citation.media_list:
-            attr_list += [str(attr.type) for attr in mref.attribute_list
-                          if attr.type.is_custom() and str(attr.type)]
-        self.media_attributes.update(attr_list)
-
-        self.source_attributes.update(
-            [str(attr.type) for attr in citation.attribute_list
-             if attr.type.is_custom() and str(attr.type)])
-
-        # Emit after added:
-        if emit:
-            self.emit(emit, ([citation.handle],))
-        self.has_changed = True
-
-    def commit_source(self, source, trans, change_time=None):
-        emit = None
-        if source.handle in self.source_map:
-            emit = "source-update"
-            self.dbapi.execute("""UPDATE source SET gramps_id = ?, 
-                                                    order_by = ?,
-                                                    blob = ? 
-                                                WHERE handle = ?;""",
-                               [source.gramps_id, 
-                                self._order_by_source_key(source),
-                                pickle.dumps(source.serialize()),
-                                source.handle])
-        else:
-            emit = "source-add"
-            self.dbapi.execute("""INSERT INTO source (handle, order_by, gramps_id, blob)
-                    VALUES(?, ?, ?, ?);""", 
-                       [source.handle, 
-                        self._order_by_source_key(source),
-                        source.gramps_id, 
-                        pickle.dumps(source.serialize())])
-        if not trans.batch:
-            self.update_backlinks(source)
-            self.dbapi.commit()
-        # Misc updates:
-        self.source_media_types.update(
-            [str(ref.media_type) for ref in source.reporef_list
-             if ref.media_type.is_custom()])       
-
-        attr_list = []
-        for mref in source.media_list:
-            attr_list += [str(attr.type) for attr in mref.attribute_list
-                          if attr.type.is_custom() and str(attr.type)]
-        self.media_attributes.update(attr_list)
-        self.source_attributes.update(
-            [str(attr.type) for attr in source.attribute_list
-             if attr.type.is_custom() and str(attr.type)])
-        # Emit after added:
-        if emit:
-            self.emit(emit, ([source.handle],))
-        self.has_changed = True
-
-    def commit_repository(self, repository, trans, change_time=None):
-        emit = None
-        if repository.handle in self.repository_map:
-            emit = "repository-update"
-            self.dbapi.execute("""UPDATE repository SET gramps_id = ?, 
-                                                    blob = ? 
-                                                WHERE handle = ?;""",
-                               [repository.gramps_id, 
-                                pickle.dumps(repository.serialize()),
-                                repository.handle])
-        else:
-            emit = "repository-add"
-            self.dbapi.execute("""INSERT INTO repository (handle, gramps_id, blob)
-                     VALUES(?, ?, ?);""", 
-                       [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())])
-        if not trans.batch:
-            self.update_backlinks(repository)
-            self.dbapi.commit()
-        # Misc updates:
-        if repository.type.is_custom():
-            self.repository_types.add(str(repository.type))
-
-        self.url_types.update([str(url.type) for url in repository.urls
-                               if url.type.is_custom()])
-        # Emit after added:
-        if emit:
-            self.emit(emit, ([repository.handle],))
-        self.has_changed = True
-
-    def commit_note(self, note, trans, change_time=None):
-        emit = None
-        if note.handle in self.note_map:
-            emit = "note-update"
-            self.dbapi.execute("""UPDATE note SET gramps_id = ?, 
-                                                    blob = ? 
-                                                WHERE handle = ?;""",
-                               [note.gramps_id, 
-                                pickle.dumps(note.serialize()),
-                                note.handle])
-        else:
-            emit = "note-add"
-            self.dbapi.execute("""INSERT INTO note (handle, gramps_id, blob)
-                     VALUES(?, ?, ?);""", 
-                       [note.handle, note.gramps_id, pickle.dumps(note.serialize())])
-        if not trans.batch:
-            self.update_backlinks(note)
-            self.dbapi.commit()
-        # Misc updates:
-        if note.type.is_custom():
-            self.note_types.add(str(note.type))        
-        # Emit after added:
-        if emit:
-            self.emit(emit, ([note.handle],))
-        self.has_changed = True
-
-    def commit_place(self, place, trans, change_time=None):
-        emit = None
-        if place.handle in self.place_map:
-            emit = "place-update"
-            self.dbapi.execute("""UPDATE place SET gramps_id = ?, 
-                                                   order_by = ?,
-                                                   blob = ? 
-                                                WHERE handle = ?;""",
-                               [place.gramps_id, 
-                                self._order_by_place_key(place),
-                                pickle.dumps(place.serialize()),
-                                place.handle])
-        else:
-            emit = "place-add"
-            self.dbapi.execute("""INSERT INTO place (handle, order_by, gramps_id, blob)
-                    VALUES(?, ?, ?, ?);""", 
-                       [place.handle, 
-                        self._order_by_place_key(place),
-                        place.gramps_id, 
-                        pickle.dumps(place.serialize())])
-        if not trans.batch:
-            self.update_backlinks(place)
-            self.dbapi.commit()
-        # Misc updates:
-        if place.get_type().is_custom():
-            self.place_types.add(str(place.get_type()))
-
-        self.url_types.update([str(url.type) for url in place.urls
-                               if url.type.is_custom()])
-
-        attr_list = []
-        for mref in place.media_list:
-            attr_list += [str(attr.type) for attr in mref.attribute_list
-                          if attr.type.is_custom() and str(attr.type)]
-        self.media_attributes.update(attr_list)
-        # Emit after added:
-        if emit:
-            self.emit(emit, ([place.handle],))
-        self.has_changed = True
-
-    def commit_event(self, event, trans, change_time=None):
-        emit = None
-        if event.handle in self.event_map:
-            emit = "event-update"
-            self.dbapi.execute("""UPDATE event SET gramps_id = ?, 
-                                                    blob = ? 
-                                                WHERE handle = ?;""",
-                               [event.gramps_id, 
-                                pickle.dumps(event.serialize()),
-                                event.handle])
-        else:
-            emit = "event-add"
-            self.dbapi.execute("""INSERT INTO event (handle, gramps_id, blob)
-                  VALUES(?, ?, ?);""", 
-                       [event.handle, 
-                        event.gramps_id, 
-                        pickle.dumps(event.serialize())])
-        if not trans.batch:
-            self.update_backlinks(event)
-            self.dbapi.commit()
-        # Misc updates:
-        self.event_attributes.update(
-            [str(attr.type) for attr in event.attribute_list
-             if attr.type.is_custom() and str(attr.type)])
-        if event.type.is_custom():
-            self.event_names.add(str(event.type))
-        attr_list = []
-        for mref in event.media_list:
-            attr_list += [str(attr.type) for attr in mref.attribute_list
-                          if attr.type.is_custom() and str(attr.type)]
-        self.media_attributes.update(attr_list)
-        # Emit after added:
-        if emit:
-            self.emit(emit, ([event.handle],))
-        self.has_changed = True
-
-    def update_backlinks(self, obj):
-        # First, delete the current references:
-        self.dbapi.execute("DELETE FROM reference WHERE obj_handle = ?;",
-                           [obj.handle])
-        # Now, add the current ones:
-        references = set(obj.get_referenced_handles_recursively())
-        for (ref_class_name, ref_handle) in references:
-            self.dbapi.execute("""INSERT INTO reference 
-                       (obj_handle, obj_class, ref_handle, ref_class)
-                       VALUES(?, ?, ?, ?);""",
-                               [obj.handle, 
-                                obj.__class__.__name__,
-                                ref_handle, 
-                                ref_class_name])
-        # This function is followed by a commit.
-
-    def commit_tag(self, tag, trans, change_time=None):
-        emit = None
-        if tag.handle in self.tag_map:
-            emit = "tag-update"
-            self.dbapi.execute("""UPDATE tag SET blob = ?,
-                                                 order_by = ?
-                                         WHERE handle = ?;""",
-                               [pickle.dumps(tag.serialize()),
-                                self._order_by_tag_key(tag),
-                                tag.handle])
-        else:
-            emit = "tag-add"
-            self.dbapi.execute("""INSERT INTO tag (handle, order_by, blob)
-                  VALUES(?, ?, ?);""", 
-                       [tag.handle, 
-                        self._order_by_tag_key(tag),
-                        pickle.dumps(tag.serialize())])
-        if not trans.batch:
-            self.update_backlinks(tag)
-            self.dbapi.commit()
-        # Emit after added:
-        if emit:
-            self.emit(emit, ([tag.handle],))
-
-    def commit_media_object(self, media, trans, change_time=None):
-        emit = None
-        if media.handle in self.media_map:
-            emit = "media-update"
-            self.dbapi.execute("""UPDATE media SET gramps_id = ?, 
-                                                   order_by = ?,
-                                                   blob = ? 
-                                                WHERE handle = ?;""",
-                               [media.gramps_id, 
-                                self._order_by_media_key(media),
-                                pickle.dumps(media.serialize()),
-                                media.handle])
-        else:
-            emit = "media-add"
-            self.dbapi.execute("""INSERT INTO media (handle, order_by, gramps_id, blob)
-                  VALUES(?, ?, ?, ?);""", 
-                       [media.handle, 
-                        self._order_by_media_key(media),
-                        media.gramps_id, 
-                        pickle.dumps(media.serialize())])
-        if not trans.batch:
-            self.update_backlinks(media)
-            self.dbapi.commit()
-        # Misc updates:
-        self.media_attributes.update(
-            [str(attr.type) for attr in media.attribute_list
-             if attr.type.is_custom() and str(attr.type)])
-        # Emit after added:
-        if emit:
-            self.emit(emit, ([media.handle],))
-
-    def get_gramps_ids(self, obj_key):
-        key2table = {
-            PERSON_KEY:     self.person_id_map, 
-            FAMILY_KEY:     self.family_id_map, 
-            CITATION_KEY:   self.citation_id_map, 
-            SOURCE_KEY:     self.source_id_map, 
-            EVENT_KEY:      self.event_id_map, 
-            MEDIA_KEY:      self.media_id_map, 
-            PLACE_KEY:      self.place_id_map, 
-            REPOSITORY_KEY: self.repository_id_map, 
-            NOTE_KEY:       self.note_id_map, 
-            }
-        return list(key2table[obj_key].keys())
-
-    def transaction_begin(self, transaction):
-        """
-        Transactions are handled automatically by the db layer.
-        """
-        return 
-
-    def set_researcher(self, owner):
-        self.owner.set_from(owner)
-
-    def get_researcher(self):
-        return self.owner
-
-    def request_rebuild(self):
-        self.emit('person-rebuild')
-        self.emit('family-rebuild')
-        self.emit('place-rebuild')
-        self.emit('source-rebuild')
-        self.emit('citation-rebuild')
-        self.emit('media-rebuild')
-        self.emit('event-rebuild')
-        self.emit('repository-rebuild')
-        self.emit('note-rebuild')
-        self.emit('tag-rebuild')
-
-    def copy_from_db(self, db):
-        """
-        A (possibily) implementation-specific method to get data from
-        db into this database.
-        """
-        for key in db._tables.keys():
-            cursor = db._tables[key]["cursor_func"]
-            class_ = db._tables[key]["class_func"]
-            for (handle, data) in cursor():
-                map = getattr(self, "%s_map" % key.lower())
-                map[handle] = class_.create(data)
-
-    def get_transaction_class(self):
-        """
-        Get the transaction class associated with this database backend.
-        """
-        return DBAPITxn
-
-    def get_from_name_and_handle(self, table_name, handle):
-        """
-        Returns a gen.lib object (or None) given table_name and
-        handle.
-
-        Examples:
-
-        >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008")
-        >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23")
-        """
-        if table_name in self._tables:
-            return self._tables[table_name]["handle_func"](handle)
-        return None
-
-    def get_from_name_and_gramps_id(self, table_name, gramps_id):
-        """
-        Returns a gen.lib object (or None) given table_name and
-        Gramps ID.
-
-        Examples:
-
-        >>> self.get_from_name_and_gramps_id("Person", "I00002")
-        >>> self.get_from_name_and_gramps_id("Family", "F056")
-        >>> self.get_from_name_and_gramps_id("Media", "M00012")
-        """
-        if table_name in self._tables:
-            return self._tables[table_name]["gramps_id_func"](gramps_id)
-        return None
-
-    def remove_person(self, handle, transaction):
-        """
-        Remove the Person specified by the database handle from the database, 
-        preserving the change in the passed transaction. 
-        """
-
-        if self.readonly or not handle:
-            return
-        if handle in self.person_map:
-            person = Person.create(self.person_map[handle])
-            #del self.person_map[handle]
-            #del self.person_id_map[person.gramps_id]
-            self.dbapi.execute("DELETE FROM person WHERE handle = ?;", [handle])
-            self.emit("person-delete", ([handle],))
-            if not transaction.batch:
-                self.dbapi.commit()
-
-    def remove_source(self, handle, transaction):
-        """
-        Remove the Source specified by the database handle from the
-        database, preserving the change in the passed transaction. 
-        """
-        self.__do_remove(handle, transaction, self.source_map, 
-                         self.source_id_map, SOURCE_KEY)
-
-    def remove_citation(self, handle, transaction):
-        """
-        Remove the Citation specified by the database handle from the
-        database, preserving the change in the passed transaction. 
-        """
-        self.__do_remove(handle, transaction, self.citation_map, 
-                         self.citation_id_map, CITATION_KEY)
-
-    def remove_event(self, handle, transaction):
-        """
-        Remove the Event specified by the database handle from the
-        database, preserving the change in the passed transaction. 
-        """
-        self.__do_remove(handle, transaction, self.event_map, 
-                         self.event_id_map, EVENT_KEY)
-
-    def remove_object(self, handle, transaction):
-        """
-        Remove the MediaObjectPerson specified by the database handle from the
-        database, preserving the change in the passed transaction. 
-        """
-        self.__do_remove(handle, transaction, self.media_map, 
-                         self.media_id_map, MEDIA_KEY)
-
-    def remove_place(self, handle, transaction):
-        """
-        Remove the Place specified by the database handle from the
-        database, preserving the change in the passed transaction. 
-        """
-        self.__do_remove(handle, transaction, self.place_map, 
-                         self.place_id_map, PLACE_KEY)
-
-    def remove_family(self, handle, transaction):
-        """
-        Remove the Family specified by the database handle from the
-        database, preserving the change in the passed transaction. 
-        """
-        self.__do_remove(handle, transaction, self.family_map, 
-                         self.family_id_map, FAMILY_KEY)
-
-    def remove_repository(self, handle, transaction):
-        """
-        Remove the Repository specified by the database handle from the
-        database, preserving the change in the passed transaction. 
-        """
-        self.__do_remove(handle, transaction, self.repository_map, 
-                         self.repository_id_map, REPOSITORY_KEY)
-
-    def remove_note(self, handle, transaction):
-        """
-        Remove the Note specified by the database handle from the
-        database, preserving the change in the passed transaction. 
-        """
-        self.__do_remove(handle, transaction, self.note_map, 
-                         self.note_id_map, NOTE_KEY)
-
-    def remove_tag(self, handle, transaction):
-        """
-        Remove the Tag specified by the database handle from the
-        database, preserving the change in the passed transaction. 
-        """
-        self.__do_remove(handle, transaction, self.tag_map, 
-                         None, TAG_KEY)
-
-    def is_empty(self):
-        """
-        Return true if there are no [primary] records in the database
-        """
-        for table in self._tables:
-            if len(self._tables[table]["handles_func"]()) > 0:
-                return False
-        return True
-
-    def __do_remove(self, handle, transaction, data_map, data_id_map, key):
-        key2table = {
-            PERSON_KEY:     "person", 
-            FAMILY_KEY:     "family", 
-            SOURCE_KEY:     "source", 
-            CITATION_KEY:   "citation", 
-            EVENT_KEY:      "event", 
-            MEDIA_KEY:      "media", 
-            PLACE_KEY:      "place", 
-            REPOSITORY_KEY: "repository", 
-            NOTE_KEY:       "note", 
-            }
-        if self.readonly or not handle:
-            return
-        if handle in data_map:
-            self.dbapi.execute("DELETE FROM %s WHERE handle = ?;" % key2table[key], 
-                               [handle])
-            self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],))
-            if not transaction.batch:
-                self.dbapi.commit()
-
-    def close(self):
-        if self._directory:
-            filename = os.path.join(self._directory, "meta_data.db")
-            touch(filename)
-            # Save metadata
-            self.set_metadata('bookmarks', self.bookmarks.get())
-            self.set_metadata('family_bookmarks', self.family_bookmarks.get())
-            self.set_metadata('event_bookmarks', self.event_bookmarks.get())
-            self.set_metadata('source_bookmarks', self.source_bookmarks.get())
-            self.set_metadata('citation_bookmarks', self.citation_bookmarks.get())
-            self.set_metadata('repo_bookmarks', self.repo_bookmarks.get())
-            self.set_metadata('media_bookmarks', self.media_bookmarks.get())
-            self.set_metadata('place_bookmarks', self.place_bookmarks.get())
-            self.set_metadata('note_bookmarks', self.note_bookmarks.get())
-            
-            # Custom type values, sets
-            self.set_metadata('event_names', self.event_names)
-            self.set_metadata('fattr_names', self.family_attributes)
-            self.set_metadata('pattr_names', self.individual_attributes)
-            self.set_metadata('sattr_names', self.source_attributes)
-            self.set_metadata('marker_names', self.marker_names)
-            self.set_metadata('child_refs', self.child_ref_types)
-            self.set_metadata('family_rels', self.family_rel_types)
-            self.set_metadata('event_roles', self.event_role_names)
-            self.set_metadata('name_types', self.name_types)
-            self.set_metadata('origin_types', self.origin_types)
-            self.set_metadata('repo_types', self.repository_types)
-            self.set_metadata('note_types', self.note_types)
-            self.set_metadata('sm_types', self.source_media_types)
-            self.set_metadata('url_types', self.url_types)
-            self.set_metadata('mattr_names', self.media_attributes)
-            self.set_metadata('eattr_names', self.event_attributes)
-            self.set_metadata('place_types', self.place_types)
-            
-            # surname list
-            self.set_metadata('surname_list', self.surname_list)
-            
-            self.dbapi.close()
-
-    def find_backlink_handles(self, handle, include_classes=None):
-        """
-        Find all objects that hold a reference to the object handle.
-        
-        Returns an interator over a list of (class_name, handle) tuples.
-
-        :param handle: handle of the object to search for.
-        :type handle: database handle
-        :param include_classes: list of class names to include in the results.
-            Default: None means include all classes.
-        :type include_classes: list of class names
-
-        Note that this is a generator function, it returns a iterator for
-        use in loops. If you want a list of the results use::
-
-            result_list = list(find_backlink_handles(handle))
-        """
-        cur = self.dbapi.execute("SELECT * FROM reference WHERE ref_handle = ?;",
-                                 [handle])
-        rows = cur.fetchall()
-        for row in rows:
-            if (include_classes is None) or (row["obj_class"] in include_classes):
-                yield (row["obj_class"], row["obj_handle"])
-
-    def find_initial_person(self):
-        handle = self.get_default_handle()
-        person = None
-        if handle:
-            person = self.get_person_from_handle(handle)
-            if person:
-                return person
-        cur = self.dbapi.execute("SELECT handle FROM person;")
-        row = cur.fetchone()
-        if row:
-            return self.get_person_from_handle(row[0])
-
-    def get_bookmarks(self):
-        return self.bookmarks
-
-    def get_citation_bookmarks(self):
-        return self.citation_bookmarks
-
-    def get_default_handle(self):
-        return self.get_metadata("default-person-handle", None)
-
-    def get_surname_list(self):
-        """
-        Return the list of locale-sorted surnames contained in the database.
-        """
-        return self.surname_list
-
-    def get_event_attribute_types(self):
-        """
-        Return a list of all Attribute types assocated with Event instances
-        in the database.
-        """
-        return list(self.event_attributes)
-
-    def get_event_types(self):
-        """
-        Return a list of all event types in the database.
-        """
-        return list(self.event_names)
-
-    def get_person_event_types(self):
-        """
-        Deprecated:  Use get_event_types
-        """
-        return list(self.event_names)
-
-    def get_person_attribute_types(self):
-        """
-        Return a list of all Attribute types assocated with Person instances 
-        in the database.
-        """
-        return list(self.individual_attributes)
-
-    def get_family_attribute_types(self):
-        """
-        Return a list of all Attribute types assocated with Family instances 
-        in the database.
-        """
-        return list(self.family_attributes)
-
-    def get_family_event_types(self):
-        """
-        Deprecated:  Use get_event_types
-        """
-        return list(self.event_names)
-
-    def get_media_attribute_types(self):
-        """
-        Return a list of all Attribute types assocated with Media and MediaRef 
-        instances in the database.
-        """
-        return list(self.media_attributes)
-
-    def get_family_relation_types(self):
-        """
-        Return a list of all relationship types assocated with Family
-        instances in the database.
-        """
-        return list(self.family_rel_types)
-
-    def get_child_reference_types(self):
-        """
-        Return a list of all child reference types assocated with Family
-        instances in the database.
-        """
-        return list(self.child_ref_types)
-
-    def get_event_roles(self):
-        """
-        Return a list of all custom event role names assocated with Event
-        instances in the database.
-        """
-        return list(self.event_role_names)
-
-    def get_name_types(self):
-        """
-        Return a list of all custom names types assocated with Person
-        instances in the database.
-        """
-        return list(self.name_types)
-
-    def get_origin_types(self):
-        """
-        Return a list of all custom origin types assocated with Person/Surname
-        instances in the database.
-        """
-        return list(self.origin_types)
-
-    def get_repository_types(self):
-        """
-        Return a list of all custom repository types assocated with Repository 
-        instances in the database.
-        """
-        return list(self.repository_types)
-
-    def get_note_types(self):
-        """
-        Return a list of all custom note types assocated with Note instances 
-        in the database.
-        """
-        return list(self.note_types)
-
-    def get_source_attribute_types(self):
-        """
-        Return a list of all Attribute types assocated with Source/Citation
-        instances in the database.
-        """
-        return list(self.source_attributes)
-
-    def get_source_media_types(self):
-        """
-        Return a list of all custom source media types assocated with Source 
-        instances in the database.
-        """
-        return list(self.source_media_types)
-
-    def get_url_types(self):
-        """
-        Return a list of all custom names types assocated with Url instances 
-        in the database.
-        """
-        return list(self.url_types)
-
-    def get_place_types(self):
-        """
-        Return a list of all custom place types assocated with Place instances
-        in the database.
-        """
-        return list(self.place_types)
-
-    def get_event_bookmarks(self):
-        return self.event_bookmarks
-
-    def get_family_bookmarks(self):
-        return self.family_bookmarks
-
-    def get_media_bookmarks(self):
-        return self.media_bookmarks
-
-    def get_note_bookmarks(self):
-        return self.note_bookmarks
-
-    def get_place_bookmarks(self):
-        return self.place_bookmarks
-
-    def get_repo_bookmarks(self):
-        return self.repo_bookmarks
-
-    def get_save_path(self):
-        return self._directory
-
-    def get_source_bookmarks(self):
-        return self.source_bookmarks
-
-    def is_open(self):
-        return self._directory is not None
-
-    def iter_citation_handles(self):
-        return (data[0] for data in self.get_citation_cursor())
-
-    def iter_citations(self):
-        return (Citation.create(data[1]) for data in self.get_citation_cursor())
-
-    def iter_event_handles(self):
-        return (data[0] for data in self.get_event_cursor())
-
-    def iter_events(self):
-        return (Event.create(data[1]) for data in self.get_event_cursor())
-
-    def iter_media_objects(self):
-        return (MediaObject.create(data[1]) for data in self.get_media_cursor())
-
-    def iter_note_handles(self):
-        return (data[0] for data in self.get_note_cursor())
-
-    def iter_notes(self):
-        return (Note.create(data[1]) for data in self.get_note_cursor())
-
-    def iter_place_handles(self):
-        return (data[0] for data in self.get_place_cursor())
-
-    def iter_places(self):
-        return (Place.create(data[1]) for data in self.get_place_cursor())
-
-    def iter_repositories(self):
-        return (Repository.create(data[1]) for data in self.get_repository_cursor())
-
-    def iter_repository_handles(self):
-        return (data[0] for data in self.get_repository_cursor())
-
-    def iter_source_handles(self):
-        return (data[0] for data in self.get_source_cursor())
-
-    def iter_sources(self):
-        return (Source.create(data[1]) for data in self.get_source_cursor())
-
-    def iter_tag_handles(self):
-        return (data[0] for data in self.get_tag_cursor())
-
-    def iter_tags(self):
-        return (Tag.create(data[1]) for data in self.get_tag_cursor())
-
-    def load(self, directory, callback=None, mode=None, 
-             force_schema_upgrade=False, 
-             force_bsddb_upgrade=False, 
-             force_bsddb_downgrade=False, 
-             force_python_upgrade=False):
-        # Run code from directory
-        default_settings = {"__file__": 
-                            os.path.join(directory, "default_settings.py")}
-        settings_file = os.path.join(directory, "default_settings.py")
-        with open(settings_file) as f:
-            code = compile(f.read(), settings_file, 'exec')
-            exec(code, globals(), default_settings)
-
-        self.dbapi = default_settings["dbapi"]
-            
-        # make sure schema is up to date:
-        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS person (
-                                    handle    TEXT PRIMARY KEY NOT NULL,
-                                    order_by  TEXT             ,
-                                    gramps_id TEXT             ,
-                                    blob      TEXT
-        );""")
-        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS family (
-                                    handle    TEXT PRIMARY KEY NOT NULL,
-                                    gramps_id TEXT             ,
-                                    blob      TEXT
-        );""")
-        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS source (
-                                    handle    TEXT PRIMARY KEY NOT NULL,
-                                    order_by  TEXT             ,
-                                    gramps_id TEXT             ,
-                                    blob      TEXT
-        );""")
-        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS citation (
-                                    handle    TEXT PRIMARY KEY NOT NULL,
-                                    order_by  TEXT             ,
-                                    gramps_id TEXT             ,
-                                    blob      TEXT
-        );""")
-        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS event (
-                                    handle    TEXT PRIMARY KEY NOT NULL,
-                                    gramps_id TEXT             ,
-                                    blob      TEXT
-        );""")
-        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS media (
-                                    handle    TEXT PRIMARY KEY NOT NULL,
-                                    order_by  TEXT             ,
-                                    gramps_id TEXT             ,
-                                    blob      TEXT
-        );""")
-        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS place (
-                                    handle    TEXT PRIMARY KEY NOT NULL,
-                                    order_by  TEXT             ,
-                                    gramps_id TEXT             ,
-                                    blob      TEXT
-        );""")
-        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS repository (
-                                    handle    TEXT PRIMARY KEY NOT NULL,
-                                    gramps_id TEXT             ,
-                                    blob      TEXT
-        );""")
-        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS note (
-                                    handle    TEXT PRIMARY KEY NOT NULL,
-                                    gramps_id TEXT             ,
-                                    blob      TEXT
-        );""")
-        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS tag (
-                                    handle    TEXT PRIMARY KEY NOT NULL,
-                                    order_by  TEXT             ,
-                                    blob      TEXT
-        );""")
-        # Secondary:
-        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS reference (
-                                    obj_handle    TEXT,
-                                    obj_class     TEXT,
-                                    ref_handle    TEXT,
-                                    ref_class     TEXT
-        );""")
-        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS name_group (
-                                    name     TEXT PRIMARY KEY NOT NULL,
-                                    grouping TEXT
-        );""")
-        self.dbapi.execute("""CREATE TABLE IF NOT EXISTS metadata (
-                                    setting  TEXT PRIMARY KEY NOT NULL,
-                                    value    TEXT
-        );""")
-        ## Indices:
-        self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
-                                  order_by ON person (order_by);
-        """)
-        self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
-                                  order_by ON source (order_by);
-        """)
-        self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
-                                  order_by ON citation (order_by);
-        """)
-        self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
-                                  order_by ON media (order_by);
-        """)
-        self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
-                                  order_by ON place (order_by);
-        """)
-        self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
-                                  order_by ON tag (order_by);
-        """)
-        self.dbapi.execute("""CREATE INDEX IF NOT EXISTS 
-                                  ref_handle ON reference (ref_handle);
-        """)
-        # Load metadata
-        self.bookmarks.set(self.get_metadata('bookmarks'))
-        self.family_bookmarks.set(self.get_metadata('family_bookmarks'))
-        self.event_bookmarks.set(self.get_metadata('event_bookmarks'))
-        self.source_bookmarks.set(self.get_metadata('source_bookmarks'))
-        self.citation_bookmarks.set(self.get_metadata('citation_bookmarks'))
-        self.repo_bookmarks.set(self.get_metadata('repo_bookmarks'))
-        self.media_bookmarks.set(self.get_metadata('media_bookmarks'))
-        self.place_bookmarks.set(self.get_metadata('place_bookmarks'))
-        self.note_bookmarks.set(self.get_metadata('note_bookmarks'))
-
-        # Custom type values
-        self.event_names = self.get_metadata('event_names', set())
-        self.family_attributes = self.get_metadata('fattr_names', set())
-        self.individual_attributes = self.get_metadata('pattr_names', set())
-        self.source_attributes = self.get_metadata('sattr_names', set())
-        self.marker_names = self.get_metadata('marker_names', set())
-        self.child_ref_types = self.get_metadata('child_refs', set())
-        self.family_rel_types = self.get_metadata('family_rels', set())
-        self.event_role_names = self.get_metadata('event_roles', set())
-        self.name_types = self.get_metadata('name_types', set())
-        self.origin_types = self.get_metadata('origin_types', set())
-        self.repository_types = self.get_metadata('repo_types', set())
-        self.note_types = self.get_metadata('note_types', set())
-        self.source_media_types = self.get_metadata('sm_types', set())
-        self.url_types = self.get_metadata('url_types', set())
-        self.media_attributes = self.get_metadata('mattr_names', set())
-        self.event_attributes = self.get_metadata('eattr_names', set())
-        self.place_types = self.get_metadata('place_types', set())
-        
-        # surname list
-        self.surname_list = self.get_metadata('surname_list')
-
-        self.set_save_path(directory)
-        self.undolog = os.path.join(self._directory, DBUNDOFN)
-        self.undodb = DBAPIUndo(self, self.undolog)
-        self.undodb.open()
-        
-    def set_prefixes(self, person, media, family, source, citation, 
-                     place, event, repository, note):
-        self.set_person_id_prefix(person)
-        self.set_object_id_prefix(media)
-        self.set_family_id_prefix(family)
-        self.set_source_id_prefix(source)
-        self.set_citation_id_prefix(citation)
-        self.set_place_id_prefix(place)
-        self.set_event_id_prefix(event)
-        self.set_repository_id_prefix(repository)
-        self.set_note_id_prefix(note)
-
-    def set_save_path(self, directory):
-        self._directory = directory
-        if directory:
-            self.full_name = os.path.abspath(self._directory)
-            self.path = self.full_name
-            self.brief_name = os.path.basename(self._directory)
-        else:
-            self.full_name = None
-            self.path = None
-            self.brief_name = None
-
-    def write_version(self, directory):
-        """Write files for a newly created DB."""
-        versionpath = os.path.join(directory, str(DBBACKEND))
-        _LOG.debug("Write database backend file to 'dbapi'")
-        with open(versionpath, "w") as version_file:
-            version_file.write("dbapi")
-        # Write default_settings, sqlite.db
-        defaults = os.path.join(os.path.dirname(os.path.abspath(__file__)),
-                                "dbapi_support", "defaults")
-        _LOG.debug("Copy defaults from: " + defaults)
-        for filename in os.listdir(defaults):
-            fullpath = os.path.abspath(os.path.join(defaults, filename))
-            shutil.copy2(fullpath, directory)
-
-    def report_bm_change(self):
-        """
-        Add 1 to the number of bookmark changes during this session.
-        """
-        self._bm_changes += 1
-
-    def db_has_bm_changes(self):
-        """
-        Return whethere there were bookmark changes during the session.
-        """
-        return self._bm_changes > 0
-
-    def get_summary(self):
-        """
-        Returns dictionary of summary item.
-        Should include, if possible:
-
-        _("Number of people")
-        _("Version")
-        _("Schema version")
-        """
-        return {
-            _("Number of people"): self.get_number_of_people(),
-        }
-
-    def get_dbname(self):
-        """
-        In DBAPI, the database is in a text file at the path
-        """
-        filepath = os.path.join(self._directory, "name.txt")
-        try:
-            name_file = open(filepath, "r")
-            name = name_file.readline().strip()
-            name_file.close()
-        except (OSError, IOError) as msg:
-            _LOG.error(str(msg))
-            name = None
-        return name
-
-    def reindex_reference_map(self, callback):
-        callback(4)
-        self.dbapi.execute("DELETE FROM reference;")
-        primary_table = (
-            (self.get_person_cursor, Person),
-            (self.get_family_cursor, Family),
-            (self.get_event_cursor, Event),
-            (self.get_place_cursor, Place),
-            (self.get_source_cursor, Source),
-            (self.get_citation_cursor, Citation),
-            (self.get_media_cursor, MediaObject),
-            (self.get_repository_cursor, Repository),
-            (self.get_note_cursor, Note),
-            (self.get_tag_cursor, Tag),
-        )
-        # Now we use the functions and classes defined above
-        # to loop through each of the primary object tables.
-        for cursor_func, class_func in primary_table:
-            logging.info("Rebuilding %s reference map" %
-                         class_func.__name__)
-            with cursor_func() as cursor:
-                for found_handle, val in cursor:
-                    obj = class_func.create(val)
-                    references = set(obj.get_referenced_handles_recursively())
-                    # handle addition of new references
-                    for (ref_class_name, ref_handle) in references:
-                        self.dbapi.execute("""INSERT INTO reference (obj_handle, obj_class, ref_handle, ref_class)
-                                                 VALUES(?, ?, ?, ?);""",
-                                           [obj.handle, 
-                                            obj.__class__.__name__,
-                                            ref_handle, 
-                                            ref_class_name])
-                                            
-        self.dbapi.commit()
-        callback(5)
-
-    def rebuild_secondary(self, update):
-        pass
-        # FIXME: rebuild the secondary databases/maps:
-        ## gender stats
-        ## event_names
-        ## fattr_names
-        ## pattr_names
-        ## sattr_names
-        ## marker_names
-        ## child_refs
-        ## family_rels
-        ## event_roles
-        ## name_types
-        ## origin_types
-        ## repo_types
-        ## note_types
-        ## sm_types
-        ## url_types
-        ## mattr_names
-        ## eattr_names
-        ## place_types
-        # surname list
-
-    def prepare_import(self):
-        """
-        Do anything needed before an import.
-        """
-        pass
-
-    def commit_import(self):
-        """
-        Do anything needed after an import.
-        """
-        self.reindex_reference_map(lambda n: n)
-
-    def has_handle_for_person(self, key):
-        cur = self.dbapi.execute("SELECT * FROM person WHERE handle = ?", [key])
-        return cur.fetchone() != None
-
-    def has_handle_for_family(self, key):
-        cur = self.dbapi.execute("SELECT * FROM family WHERE handle = ?", [key])
-        return cur.fetchone() != None
-
-    def has_handle_for_source(self, key):
-        cur = self.dbapi.execute("SELECT * FROM source WHERE handle = ?", [key])
-        return cur.fetchone() != None
-
-    def has_handle_for_citation(self, key):
-        cur = self.dbapi.execute("SELECT * FROM citation WHERE handle = ?", [key])
-        return cur.fetchone() != None
-
-    def has_handle_for_event(self, key):
-        cur = self.dbapi.execute("SELECT * FROM event WHERE handle = ?", [key])
-        return cur.fetchone() != None
-
-    def has_handle_for_media(self, key):
-        cur = self.dbapi.execute("SELECT * FROM media WHERE handle = ?", [key])
-        return cur.fetchone() != None
-
-    def has_handle_for_place(self, key):
-        cur = self.dbapi.execute("SELECT * FROM place WHERE handle = ?", [key])
-        return cur.fetchone() != None
-
-    def has_handle_for_repository(self, key):
-        cur = self.dbapi.execute("SELECT * FROM repository WHERE handle = ?", [key])
-        return cur.fetchone() != None
-
-    def has_handle_for_note(self, key):
-        cur = self.dbapi.execute("SELECT * FROM note WHERE handle = ?", [key])
-        return cur.fetchone() != None
-
-    def has_handle_for_tag(self, key):
-        cur = self.dbapi.execute("SELECT * FROM tag WHERE handle = ?", [key])
-        return cur.fetchone() != None
-
-    def has_gramps_id_for_person(self, key):
-        cur = self.dbapi.execute("SELECT * FROM person WHERE gramps_id = ?", [key])
-        return cur.fetchone() != None
-
-    def has_gramps_id_for_family(self, key):
-        cur = self.dbapi.execute("SELECT * FROM family WHERE gramps_id = ?", [key])
-        return cur.fetchone() != None
-
-    def has_gramps_id_for_source(self, key):
-        cur = self.dbapi.execute("SELECT * FROM source WHERE gramps_id = ?", [key])
-        return cur.fetchone() != None
-
-    def has_gramps_id_for_citation(self, key):
-        cur = self.dbapi.execute("SELECT * FROM citation WHERE gramps_id = ?", [key])
-        return cur.fetchone() != None
-
-    def has_gramps_id_for_event(self, key):
-        cur = self.dbapi.execute("SELECT * FROM event WHERE gramps_id = ?", [key])
-        return cur.fetchone() != None
-
-    def has_gramps_id_for_media(self, key):
-        cur = self.dbapi.execute("SELECT * FROM media WHERE gramps_id = ?", [key])
-        return cur.fetchone() != None
-
-    def has_gramps_id_for_place(self, key):
-        cur = self.dbapi.execute("SELECT * FROM place WHERE gramps_id = ?", [key])
-        return cur.fetchone() != None
-
-    def has_gramps_id_for_repository(self, key):
-        cur = self.dbapi.execute("SELECT * FROM repository WHERE gramps_id = ?", [key])
-        return cur.fetchone() != None
-
-    def has_gramps_id_for_note(self, key):
-        cur = self.dbapi.execute("SELECT * FROM note WHERE gramps_id = ?", [key])
-        return cur.fetchone() != None
-
-    def get_person_gramps_ids(self):
-        cur = self.dbapi.execute("SELECT gramps_id FROM person;")
-        rows = cur.fetchall()
-        return [row[0] for row in rows]
-
-    def get_family_gramps_ids(self):
-        cur = self.dbapi.execute("SELECT gramps_id FROM family;")
-        rows = cur.fetchall()
-        return [row[0] for row in rows]
-
-    def get_source_gramps_ids(self):
-        cur = self.dbapi.execute("SELECT gramps_id FROM source;")
-        rows = cur.fetchall()
-        return [row[0] for row in rows]
-
-    def get_citation_gramps_ids(self):
-        cur = self.dbapi.execute("SELECT gramps_id FROM citation;")
-        rows = cur.fetchall()
-        return [row[0] for row in rows]
-
-    def get_event_gramps_ids(self):
-        cur = self.dbapi.execute("SELECT gramps_id FROM event;")
-        rows = cur.fetchall()
-        return [row[0] for row in rows]
-
-    def get_media_gramps_ids(self):
-        cur = self.dbapi.execute("SELECT gramps_id FROM media;")
-        rows = cur.fetchall()
-        return [row[0] for row in rows]
-
-    def get_place_gramps_ids(self):
-        cur = self.dbapi.execute("SELECT gramps FROM place;")
-        rows = cur.fetchall()
-        return [row[0] for row in rows]
-
-    def get_repository_gramps_ids(self):
-        cur = self.dbapi.execute("SELECT gramps_id FROM repository;")
-        rows = cur.fetchall()
-        return [row[0] for row in rows]
-
-    def get_note_gramps_ids(self):
-        cur = self.dbapi.execute("SELECT gramps_id FROM note;")
-        rows = cur.fetchall()
-        return [row[0] for row in rows]
-
-    def _get_raw_person_data(self, key):
-        cur = self.dbapi.execute("SELECT blob FROM person WHERE handle = ?", [key])
-        row = cur.fetchone()
-        if row:
-            return pickle.loads(row[0])
-
-    def _get_raw_family_data(self, key):
-        cur = self.dbapi.execute("SELECT blob FROM family WHERE handle = ?", [key])
-        row = cur.fetchone()
-        if row:
-            return pickle.loads(row[0])
-
-    def _get_raw_source_data(self, key):
-        cur = self.dbapi.execute("SELECT blob FROM source WHERE handle = ?", [key])
-        row = cur.fetchone()
-        if row:
-            return pickle.loads(row[0])
-
-    def _get_raw_citation_data(self, key):
-        cur = self.dbapi.execute("SELECT blob FROM citation WHERE handle = ?", [key])
-        row = cur.fetchone()
-        if row:
-            return pickle.loads(row[0])
-
-    def _get_raw_event_data(self, key):
-        cur = self.dbapi.execute("SELECT blob FROM event WHERE handle = ?", [key])
-        row = cur.fetchone()
-        if row:
-            return pickle.loads(row[0])
-
-    def _get_raw_media_data(self, key):
-        cur = self.dbapi.execute("SELECT blob FROM media WHERE handle = ?", [key])
-        row = cur.fetchone()
-        if row:
-            return pickle.loads(row[0])
-
-    def _get_raw_place_data(self, key):
-        cur = self.dbapi.execute("SELECT blob FROM place WHERE handle = ?", [key])
-        row = cur.fetchone()
-        if row:
-            return pickle.loads(row[0])
-
-    def _get_raw_repository_data(self, key):
-        cur = self.dbapi.execute("SELECT blob FROM repository WHERE handle = ?", [key])
-        row = cur.fetchone()
-        if row:
-            return pickle.loads(row[0])
-
-    def _get_raw_note_data(self, key):
-        cur = self.dbapi.execute("SELECT blob FROM note WHERE handle = ?", [key])
-        row = cur.fetchone()
-        if row:
-            return pickle.loads(row[0])
-
-    def _get_raw_tag_data(self, key):
-        cur = self.dbapi.execute("SELECT blob FROM tag WHERE handle = ?", [key])
-        row = cur.fetchone()
-        if row:
-            return pickle.loads(row[0])
-
-    def _order_by_person_key(self, person):
-        """
-        All non pa/matronymic surnames are used in indexing.
-        pa/matronymic not as they change for every generation!
-        returns a byte string
-        """
-        if person.primary_name and person.primary_name.surname_list:
-            order_by = " ".join([x.surname for x in person.primary_name.surname_list if not 
-                                 (int(x.origintype) in [NameOriginType.PATRONYMIC, 
-                                                        NameOriginType.MATRONYMIC]) ])
-        else:
-            order_by = ""
-        return glocale.sort_key(order_by)
-
-    def _order_by_place_key(self, place):
-        return glocale.sort_key(place.title)
-
-    def _order_by_source_key(self, source):
-        return glocale.sort_key(source.title)
-
-    def _order_by_citation_key(self, citation):
-        return glocale.sort_key(citation.page)
-
-    def _order_by_media_key(self, media):
-        return glocale.sort_key(media.desc)
-
-    def _order_by_tag_key(self, tag):
-        return glocale.sort_key(tag.get_name())
-
-    def backup(self):
-        """
-        If you wish to support an optional backup routine, put it here.
-        """
-        from gramps.plugins.export.exportxml import XmlWriter
-        from gramps.cli.user import User 
-        writer = XmlWriter(self, User(), strip_photos=0, compress=1)
-        filename = os.path.join(self._directory, "data.gramps")
-        writer.write(filename)
-
-    def restore(self):
-        """
-        If you wish to support an optional restore routine, put it here.
-        """
-        pass
-
-    def get_undodb(self):
-        return self.undodb
-
-    def undo(self, update_history=True):
-        return self.undodb.undo(update_history)
-
-    def redo(self, update_history=True):
-        return self.undodb.redo(update_history)
-
-    def get_dbid(self):
-        """
-        We use the file directory name as the unique ID for
-        this database on this computer.
-        """
-        return self.brief_name
diff --git a/gramps/plugins/database/dbapi_support/defaults/default_settings.py b/gramps/plugins/database/dbapi_support/defaults/default_settings.py
deleted file mode 100644
index 8931c8e89..000000000
--- a/gramps/plugins/database/dbapi_support/defaults/default_settings.py
+++ /dev/null
@@ -1,7 +0,0 @@
-import os
-import sqlite3
-
-path_to_db = os.path.join(os.path.dirname(os.path.realpath(__file__)),
-                        'sqlite.db')  
-dbapi = sqlite3.connect(path_to_db)
-dbapi.row_factory = sqlite3.Row # allows access by name
diff --git a/gramps/plugins/database/dictionarydb.gpr.py b/gramps/plugins/database/dictionarydb.gpr.py
index 3b0e62eca..d035de58a 100644
--- a/gramps/plugins/database/dictionarydb.gpr.py
+++ b/gramps/plugins/database/dictionarydb.gpr.py
@@ -24,7 +24,7 @@ plg.name  = _("Dictionary Database Backend")
 plg.name_accell  = _("Di_ctionary Database Backend")
 plg.description =  _("Dictionary (in-memory) Database Backend")
 plg.version = '1.0'
-plg.gramps_target_version = "4.2"
+plg.gramps_target_version = "5.0"
 plg.status = STABLE
 plg.fname = 'dictionarydb.py'
 plg.ptype = DATABASE
diff --git a/gramps/plugins/database/django_support/defaults/default_settings.py b/gramps/plugins/database/django_support/defaults/default_settings.py
deleted file mode 100644
index fae3ffd06..000000000
--- a/gramps/plugins/database/django_support/defaults/default_settings.py
+++ /dev/null
@@ -1,150 +0,0 @@
-import os
-from gramps.gen.const import DATA_DIR
-
-WEB_DIR = os.path.dirname(os.path.realpath(__file__))
-
-DEBUG = True
-TEMPLATE_DEBUG = DEBUG
-
-INTERNAL_IPS = ('127.0.0.1',)
-
-ADMINS = (
-    ('admin', 'your_email@domain.com'),
-)
-
-MANAGERS = ADMINS
-DATABASE_ROUTERS = []
-DATABASES = {
-    'default': {
-        'ENGINE': 'django.db.backends.sqlite3',
-        'NAME': os.path.join(WEB_DIR, 'sqlite.db'),
-    }
-}
-DATABASE_ENGINE = 'sqlite3'           
-DATABASE_NAME = os.path.join(WEB_DIR, 'sqlite.db')
-DATABASE_USER = ''             
-DATABASE_PASSWORD = ''         
-DATABASE_HOST = ''             
-DATABASE_PORT = ''             
-TIME_ZONE = 'America/New_York'
-LANGUAGE_CODE = 'en-us'
-SITE_ID = 1
-USE_I18N = True
-MEDIA_ROOT = ''
-MEDIA_URL = ''
-ADMIN_MEDIA_PREFIX = '/gramps-media/'
-SECRET_KEY = 'zd@%vslj5sqhx94_8)0hsx*rk9tj3^ly$x+^*tq4bggr&uh$ac'
-
-TEMPLATE_LOADERS = (
-    'django.template.loaders.filesystem.Loader', # 1.4
-    'django.template.loaders.app_directories.Loader', # 1.4
-   #'django.template.loaders.filesystem.load_template_source',
-   #'django.template.loaders.app_directories.load_template_source',
-)
-
-MIDDLEWARE_CLASSES = (
-    'django.middleware.csrf.CsrfViewMiddleware',
-    'django.middleware.common.CommonMiddleware',
-    'django.contrib.sessions.middleware.SessionMiddleware',
-    'django.contrib.auth.middleware.AuthenticationMiddleware',
-    'django.contrib.messages.middleware.MessageMiddleware',
-#   'debug_toolbar.middleware.DebugToolbarMiddleware',
-)
-
-ROOT_URLCONF = 'gramps.webapp.urls'
-STATIC_URL = '/static/' # 1.4
-
-TEMPLATE_DIRS = (
-    # Use absolute paths, not relative paths.
-    os.path.join(DATA_DIR, "templates"),
-)
-
-TEMPLATE_CONTEXT_PROCESSORS = (
-    "django.contrib.auth.context_processors.auth", # 1.4
-    "django.contrib.messages.context_processors.messages", # 1.4
-#   "django.core.context_processors.auth",
-#   "django.core.context_processors.debug",
-    "django.core.context_processors.i18n",
-    "django.core.context_processors.media",
-    "gramps.webapp.grampsdb.views.context_processor",
-    "gramps.webapp.context.messages",
-)
-
-INSTALLED_APPS = (
-    'django.contrib.auth',
-    'django.contrib.contenttypes',
-    'django.contrib.sessions',
-    'django.contrib.staticfiles',
-    'django.contrib.messages', # 1.4
-    'django.contrib.sites',
-    'django.contrib.admin',
-    'gramps.webapp.grampsdb',
-#    'django_extensions',
-#    'debug_toolbar',
-)
-
-DEBUG_TOOLBAR_PANELS = (
-    'debug_toolbar.panels.version.VersionDebugPanel',
-    'debug_toolbar.panels.timer.TimerDebugPanel',
-    'debug_toolbar.panels.settings_vars.SettingsVarsDebugPanel',
-    'debug_toolbar.panels.headers.HeaderDebugPanel',
-    'debug_toolbar.panels.request_vars.RequestVarsDebugPanel',
-    'debug_toolbar.panels.template.TemplateDebugPanel',
-    'debug_toolbar.panels.sql.SQLDebugPanel',
-    'debug_toolbar.panels.signals.SignalDebugPanel',
-    'debug_toolbar.panels.logger.LoggingPanel',
-    )
-
-def custom_show_toolbar(request):
-    return True # Always show toolbar, for example purposes only.
-
-DEBUG_TOOLBAR_CONFIG = {
-    'INTERCEPT_REDIRECTS': False,
-#    'SHOW_TOOLBAR_CALLBACK': custom_show_toolbar,
-#    'EXTRA_SIGNALS': ['myproject.signals.MySignal'],
-    'HIDE_DJANGO_SQL': False,
-    }
-
-AUTH_PROFILE_MODULE = "grampsdb.Profile"
-
-# Had to add these to use settings.configure():
-DATABASE_OPTIONS = ''
-URL_VALIDATOR_USER_AGENT = ''
-DEFAULT_INDEX_TABLESPACE = ''
-DEFAULT_TABLESPACE = ''
-CACHE_BACKEND = 'locmem://'
-TRANSACTIONS_MANAGED = False
-LOCALE_PATHS = tuple()
-
-# Changes for Django 1.3:
-USE_L10N = True
-FORMAT_MODULE_PATH = ""
-## End Changes for Django 1.3
-
-# Changes for Django 1.4:
-USE_TZ = False
-## End Changes for Django 1.4
-
-# Changes for Django 1.5:
-CACHES = {
-    'default': {
-        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
-        }
-    }
-DEFAULT_CHARSET = "utf-8"
-## End Changes for Django 1.5
-
-## Changes for Django 1.5.4:
-LOGGING_CONFIG = None
-AUTH_USER_MODEL = 'auth.User'
-## End Changes for Django 1.5.4
-
-LOGIN_URL = "/login/"
-LOGOUT_URL = "/logout"
-LOGIN_REDIRECT_URL = "/"
-
-## Changes for Django 1.6:
-LOGGING = None
-
-## Changes for Django 1.7.1:
-ABSOLUTE_URL_OVERRIDES = {}
diff --git a/gramps/plugins/database/django_support/defaults/sqlite.db b/gramps/plugins/database/django_support/defaults/sqlite.db
deleted file mode 100644
index a9ac911f065f752dfb25be75e2916ff5df508451..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 293888
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
N%rWbcAX@iQ{{@Gr__6>1

diff --git a/gramps/plugins/database/django_support/libdjango.py b/gramps/plugins/database/django_support/libdjango.py
deleted file mode 100644
index fa0a596c8..000000000
--- a/gramps/plugins/database/django_support/libdjango.py
+++ /dev/null
@@ -1,2063 +0,0 @@
-# Gramps - a GTK+/GNOME based genealogy program
-#
-# Copyright (C) 2009         Douglas S. Blank <doug.blank@gmail.com>
-#
-# 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; either version 2 of the License, or
-# (at your option) any later version.
-#
-# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-
-""" Interface to Django models """
-
-#------------------------------------------------------------------------
-#
-# Python Modules
-#
-#------------------------------------------------------------------------
-import time
-import sys
-import pickle
-import base64
-import collections
-
-#------------------------------------------------------------------------
-#
-# Django Modules
-#
-#------------------------------------------------------------------------
-from django.contrib.contenttypes.models import ContentType
-from django.db import transaction
-
-#------------------------------------------------------------------------
-#
-# Gramps Modules
-#
-#------------------------------------------------------------------------
-import gramps.webapp.grampsdb.models as models
-from gramps.gen.lib import Name
-from gramps.gen.utils.id import create_id
-from gramps.gen.constfunc import conv_to_unicode
-
-# To get a django person from a django database:
-#    djperson = dji.Person.get(handle='djhgsdh324hjg234hj24')
-# 
-# To turn the djperson into a Gramps Person:
-#    tuple = dji.get_person(djperson)
-#    gperson = lib.gen.Person(tuple)
-# OR
-#    gperson = dbdjango.DbDjango().get_person_from_handle(handle)
-
-def check_diff(item, raw):
-    encoded = str(base64.encodebytes(pickle.dumps(raw)), "utf-8")
-    if item.cache != encoded:
-        print("Different:", item.__class__.__name__, item.gramps_id)
-        print("raw  :", raw)
-        print("cache:", item.from_cache())
-        # FIXING, TOO:
-        item.save_cache()
-
-#-------------------------------------------------------------------------
-#
-# Import functions
-#
-#-------------------------------------------------------------------------
-def lookup_role_index(role0, event_ref_list):
-    """
-    Find the handle in a unserialized event_ref_list and return code.
-    """
-    if role0 is None:
-        return -1
-    else:
-        count = 0
-        for event_ref in event_ref_list:
-            (private, note_list, attribute_list, ref, erole) = event_ref
-            try:
-                event = models.Event.objects.get(handle=ref)
-            except:
-                return -1
-            if event.event_type[0] == role0:
-                return count
-            count += 1
-        return -1
-
-def totime(dtime):
-    if dtime:
-        return int(time.mktime(dtime.timetuple()))
-    else:
-        return 0
-
-#-------------------------------------------------------------------------
-#
-# Export functions
-#
-#-------------------------------------------------------------------------
-def todate(t):
-    return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(t))
-
-def lookup(index, event_ref_list):
-    """
-    Get the unserialized event_ref in an list of them and return it.
-    """
-    if index < 0:
-        return None
-    else:
-        count = 0
-        for event_ref in event_ref_list:
-            (private, note_list, attribute_list, ref, role) = event_ref
-            if index == count:
-                return ref
-            count += 1
-        return None
-
-def get_datamap(grampsclass):
-    return [x[0] for x in grampsclass._DATAMAP if x[0] != grampsclass.CUSTOM]
-
-#-------------------------------------------------------------------------
-#
-# Django Interface
-#
-#-------------------------------------------------------------------------
-class DjangoInterface(object):
-    """
-    DjangoInterface for interoperating between Gramps and Django.
-    
-    This interface comes in a number of parts: 
-        get_ITEMS()
-        add_ITEMS()
-
-    get_ITEM(ITEM)
-
-    Given an ITEM from a Django table, construct a Gramps Raw Data tuple.
-
-    add_ITEM(data)
-
-    Given a Gramps Raw Data tuple, add the data to the Django tables.
-    
-
-    """
-    def __init__(self):
-        self.debug = 0
-
-    def __getattr__(self, name):
-        """
-        Django Objects database interface.
-
-        >>> self.Person.all()
-        >>> self.Person.get(id=1)
-        >>> self.Person.get(handle='gh71234dhf3746347734')
-        """
-        if hasattr(models, name):
-            return getattr(models, name).objects
-        else:
-            raise AttributeError("no such model: '%s'" % name)
-
-    def get_next_id(self, obj, prefix):
-        """
-        Get next gramps_id
-
-        >>> dji.get_next_id(Person, "P")
-        'P0002'
-        >>> dji.get_next_id(Media, "M")
-        'M2349'
-        """
-        ids = [o["gramps_id"] for o in obj.objects.values("gramps_id")]
-        count = 1
-        while "%s%04d" % (prefix, count) in ids: 
-            count += 1
-        return "%s%04d" % (prefix, count)
-
-    def get_model(self, name):
-        if hasattr(models, name):
-            return getattr(models, name)
-        elif hasattr(models, name.title()):
-            return getattr(models, name.title())
-        else:
-            raise AttributeError("no such model: '%s'" % name)
-
-    # -----------------------------------------------
-    # Get methods to retrieve list data from the tables
-    # -----------------------------------------------
-
-    def clear_tables(self, *args):
-        return models.clear_tables(*args)
-
-    def get_tag_list(self, obj):
-        return obj.get_tag_list()
-
-    def get_attribute_list(self, obj):
-        obj_type = ContentType.objects.get_for_model(obj)
-        attribute_list = models.Attribute.objects.filter(object_id=obj.id, 
-                                                     object_type=obj_type)
-        return list(map(self.pack_attribute, attribute_list))
-
-    def get_primary_name(self, person):
-        names = person.name_set.filter(preferred=True).order_by("order")
-        if len(names) > 0:
-            return Name.create(self.pack_name(names[0]))
-        else:
-            return Name()
-      
-    def get_alternate_names(self, person):
-        names = person.name_set.filter(preferred=False).order_by("order")
-        return [Name.create(self.pack_name(n)) for n in names]
-
-    def get_names(self, person, preferred):
-        names = person.name_set.filter(preferred=preferred).order_by("order")
-        if preferred:
-            if len(names) > 0:
-                return self.pack_name(names[0])
-            else:
-                return Name().serialize()
-        else:
-            return list(map(self.pack_name, names))
-     
-    def get_source_attribute_list(self, source): 
-        return [(map.private, map.key, map.value) for map in source.sourceattribute_set.all().order_by("order")]
-
-    def get_citation_attribute_list(self, citation): 
-        return [(map.private, map.key, map.value) for map in citation.citationattribute_set.all().order_by("order")]
-
-    def get_media_list(self, obj):
-        obj_type = ContentType.objects.get_for_model(obj)
-        mediarefs = models.MediaRef.objects.filter(object_id=obj.id, 
-                                               object_type=obj_type)
-        return list(map(self.pack_media_ref, mediarefs))
-
-    def get_note_list(self, obj):
-        obj_type = ContentType.objects.get_for_model(obj)
-        noterefs = models.NoteRef.objects.filter(object_id=obj.id, 
-                                             object_type=obj_type)
-        return [noteref.ref_object.handle for noteref in noterefs]
-
-    def get_repository_ref_list(self, obj):
-        obj_type = ContentType.objects.get_for_model(obj)
-        reporefs = models.RepositoryRef.objects.filter(object_id=obj.id, 
-                                                   object_type=obj_type)
-        return list(map(self.pack_repository_ref, reporefs))
-
-    def get_place_ref_list(self, obj):
-        obj_type = ContentType.objects.get_for_model(obj)
-        refs = models.PlaceRef.objects.filter(object_id=obj.id, 
-                                              object_type=obj_type)
-        return list(map(self.pack_place_ref, refs))
-
-    def get_url_list(self, obj):
-        return list(map(self.pack_url, obj.url_set.all().order_by("order")))
-
-    def get_address_list(self, obj, with_parish): # person or repository
-        addresses = obj.address_set.all().order_by("order")
-        return [self.pack_address(address, with_parish)
-                    for address in addresses]
-
-    def get_child_ref_list(self, family):
-        obj_type = ContentType.objects.get_for_model(family)
-        childrefs = models.ChildRef.objects.filter(object_id=family.id,
-                                        object_type=obj_type).order_by("order")
-        return list(map(self.pack_child_ref, childrefs))
-
-    def get_citation_list(self, obj):
-        obj_type = ContentType.objects.get_for_model(obj)
-        citationrefs = models.CitationRef.objects.filter(object_id=obj.id,
-                                                         object_type=obj_type).order_by("order")
-        return [citationref.citation.handle for citationref in citationrefs]
-
-    def get_event_refs(self, obj, order="order"):
-        obj_type = ContentType.objects.get_for_model(obj)
-        eventrefs = models.EventRef.objects.filter(object_id=obj.id,
-                                        object_type=obj_type).order_by(order)
-        return eventrefs
-
-    def get_event_ref_list(self, obj):
-        obj_type = ContentType.objects.get_for_model(obj)
-        eventrefs = models.EventRef.objects.filter(object_id=obj.id,
-                                        object_type=obj_type).order_by("order")
-        return list(map(self.pack_event_ref, eventrefs))
-
-    def get_family_list(self, person): # person has families
-        return [fam.family.handle for fam in 
-                models.MyFamilies.objects.filter(person=person).order_by("order")]
-    
-    def get_parent_family_list(self, person): # person's parents has families
-        return [fam.family.handle for fam in 
-                models.MyParentFamilies.objects.filter(person=person).order_by("order")]
-
-    def get_person_ref_list(self, person):
-        obj_type = ContentType.objects.get_for_model(person)
-        return list(map(self.pack_person_ref, 
-                models.PersonRef.objects.filter(object_id=person.id, 
-                                            object_type=obj_type)))
-
-    def get_lds_list(self, obj): # person or family
-        return list(map(self.pack_lds, obj.lds_set.all().order_by("order")))
-
-    def get_place_handle(self, obj): # obj is event
-        if obj.place:
-            return obj.place.handle
-        return ''
-
-    ## Packers:
-
-    def get_event(self, event):
-        handle = event.handle
-        gid = event.gramps_id
-        the_type = tuple(event.event_type)
-        description = event.description
-        change = totime(event.last_changed)
-        private = event.private
-        note_list = self.get_note_list(event)           
-        citation_list = self.get_citation_list(event)   
-        media_list = self.get_media_list(event)         
-        attribute_list = self.get_attribute_list(event)
-        date = self.get_date(event)
-        place_handle = self.get_place_handle(event)
-        tag_list = self.get_tag_list(event)
-        return (str(handle), gid, the_type, date, description, place_handle, 
-                citation_list, note_list, media_list, attribute_list,
-                change, tag_list, private)
-
-    def get_note_markup(self, note):
-        retval = []
-        markups = models.Markup.objects.filter(note=note).order_by("order")
-        for markup in markups:
-            if markup.string and markup.string.isdigit():
-                value = int(markup.string)
-            else:
-                value = markup.string 
-            start_stop_list  = markup.start_stop_list
-            ss_list = eval(start_stop_list)
-            retval += [(tuple(markup.styled_text_tag_type), value, ss_list)]
-        return retval
-
-    def get_tag(self, tag):
-        changed = totime(tag.last_changed)
-        return (str(tag.handle),
-                tag.name,
-                tag.color,
-                tag.priority,
-                changed)
-
-    def get_note(self, note):
-        styled_text = [note.text, self.get_note_markup(note)]
-        changed = totime(note.last_changed)
-        tag_list = self.get_tag_list(note)
-        return (str(note.handle), 
-                note.gramps_id, 
-                styled_text, 
-                note.preformatted, 
-                tuple(note.note_type), 
-                changed, 
-                tag_list, 
-                note.private)
-
-    def get_family(self, family):
-        child_ref_list = self.get_child_ref_list(family)
-        event_ref_list = self.get_event_ref_list(family)
-        media_list = self.get_media_list(family)
-        attribute_list = self.get_attribute_list(family)
-        lds_seal_list = self.get_lds_list(family)
-        citation_list = self.get_citation_list(family)
-        note_list = self.get_note_list(family)
-        tag_list = self.get_tag_list(family)
-        if family.father:
-            father_handle = family.father.handle
-        else:
-            father_handle = ''
-        if family.mother:
-            mother_handle = family.mother.handle
-        else:
-            mother_handle = ''
-        return (str(family.handle), family.gramps_id, 
-                father_handle, mother_handle,
-                child_ref_list, tuple(family.family_rel_type), 
-                event_ref_list, media_list,
-                attribute_list, lds_seal_list, 
-                citation_list, note_list,
-                totime(family.last_changed), 
-                tag_list, 
-                family.private)
-
-    def get_repository(self, repository):
-        note_list = self.get_note_list(repository)
-        address_list = self.get_address_list(repository, with_parish=False)
-        url_list = self.get_url_list(repository)
-        tag_list = self.get_tag_list(repository)
-        return (str(repository.handle), 
-                repository.gramps_id, 
-                tuple(repository.repository_type),
-                repository.name, 
-                note_list,
-                address_list, 
-                url_list, 
-                totime(repository.last_changed), 
-                tag_list,
-                repository.private)
-
-    def get_citation(self, citation):
-        note_list = self.get_note_list(citation)
-        media_list = self.get_media_list(citation)
-        attribute_list = self.get_citation_attribute_list(citation)
-        tag_list = self.get_tag_list(citation)
-        date = self.get_date(citation)
-        # I guess citations can have no source
-        if citation.source:
-            handle = citation.source.handle
-        else:
-            handle = None
-        return (str(citation.handle),  
-                citation.gramps_id, 
-                date,
-                citation.page, 
-                citation.confidence, 
-                handle,
-                note_list, 
-                media_list, 
-                attribute_list, 
-                totime(citation.last_changed), 
-                tag_list,
-                citation.private) 
-
-    def get_source(self, source):
-        note_list = self.get_note_list(source)
-        media_list = self.get_media_list(source)
-        attribute_list = self.get_source_attribute_list(source)
-        reporef_list = self.get_repository_ref_list(source)
-        tag_list = self.get_tag_list(source)
-        return (str(source.handle), 
-                source.gramps_id, 
-                source.title,
-                source.author, 
-                source.pubinfo,
-                note_list,
-                media_list,
-                source.abbrev,
-                totime(source.last_changed), 
-                attribute_list,
-                reporef_list,
-                tag_list,
-                source.private)
-
-    def get_media(self, media):
-        attribute_list = self.get_attribute_list(media)
-        citation_list = self.get_citation_list(media)
-        note_list = self.get_note_list(media)
-        tag_list = self.get_tag_list(media)
-        date = self.get_date(media)
-        return (str(media.handle), 
-                media.gramps_id, 
-                conv_to_unicode(media.path, None),
-                str(media.mime), 
-                str(media.desc),
-                media.checksum,
-                attribute_list,
-                citation_list,
-                note_list,
-                totime(media.last_changed),
-                date,
-                tag_list,
-                media.private)
-
-    def get_person(self, person):
-        primary_name = self.get_names(person, True) # one
-        alternate_names = self.get_names(person, False) # list
-        event_ref_list = self.get_event_ref_list(person)
-        family_list = self.get_family_list(person)
-        parent_family_list = self.get_parent_family_list(person)
-        media_list = self.get_media_list(person)
-        address_list = self.get_address_list(person, with_parish=False)
-        attribute_list = self.get_attribute_list(person)
-        url_list = self.get_url_list(person)
-        lds_ord_list = self.get_lds_list(person)
-        pcitation_list = self.get_citation_list(person)
-        pnote_list = self.get_note_list(person)
-        person_ref_list = self.get_person_ref_list(person)
-        # This looks up the events for the first EventType given:
-        death_ref_index = person.death_ref_index
-        birth_ref_index = person.birth_ref_index
-        tag_list = self.get_tag_list(person)
-
-        return (str(person.handle),
-                person.gramps_id,  
-                tuple(person.gender_type)[0],
-                primary_name,       
-                alternate_names,    
-                death_ref_index,    
-                birth_ref_index,    
-                event_ref_list,     
-                family_list,        
-                parent_family_list, 
-                media_list,         
-                address_list,       
-                attribute_list,     
-                url_list,               
-                lds_ord_list,       
-                pcitation_list,       
-                pnote_list,         
-                totime(person.last_changed),             
-                tag_list, 
-                person.private,            
-                person_ref_list)
-
-    def get_date(self, obj):
-        if ((obj.calendar == obj.modifier == obj.quality == obj.sortval == obj.newyear == 0) and
-            obj.text == "" and (not obj.slash1) and (not obj.slash2) and 
-            (obj.day1 == obj.month1 == obj.year1 == 0) and 
-            (obj.day2 == obj.month2 == obj.year2 == 0)):
-            return None
-        elif ((not obj.slash1) and (not obj.slash2) and 
-            (obj.day2 == obj.month2 == obj.year2 == 0)):
-            dateval = (obj.day1, obj.month1, obj.year1, obj.slash1)
-        else:
-            dateval = (obj.day1, obj.month1, obj.year1, obj.slash1, 
-                       obj.day2, obj.month2, obj.year2, obj.slash2)
-        return (obj.calendar, obj.modifier, obj.quality, dateval, 
-                obj.text, obj.sortval, obj.newyear)
-
-    def get_place(self, place):
-        locations = place.location_set.all().order_by("order")
-        alt_location_list = [self.pack_location(location, True) for location in locations]
-        url_list = self.get_url_list(place)
-        media_list = self.get_media_list(place)
-        citation_list = self.get_citation_list(place)
-        note_list = self.get_note_list(place)
-        tag_list = self.get_tag_list(place)
-        place_ref_list = self.get_place_ref_list(place)
-        return (str(place.handle), 
-                place.gramps_id,
-                place.title, 
-                place.long, 
-                place.lat,
-                place_ref_list,
-                place.name,
-                [], ## FIXME: get_alt_names
-                tuple(place.place_type),
-                place.code,
-                alt_location_list,
-                url_list,
-                media_list,
-                citation_list,
-                note_list,
-                totime(place.last_changed), 
-                tag_list,
-                place.private)
-
-    # ---------------------------------
-    # Packers
-    # ---------------------------------
-
-    ## The packers build GRAMPS raw unserialized data.
-
-    ## Reference packers
-
-    def pack_child_ref(self, child_ref):
-        citation_list = self.get_citation_list(child_ref)
-        note_list = self.get_note_list(child_ref) 
-        return (child_ref.private, citation_list, note_list, child_ref.ref_object.handle, 
-                tuple(child_ref.father_rel_type), tuple(child_ref.mother_rel_type))
-
-    def pack_person_ref(self, personref):
-        citation_list = self.get_citation_list(personref)
-        note_list = self.get_note_list(personref)
-        return (personref.private, 
-                citation_list,
-                note_list,
-                personref.ref_object.handle,
-                personref.description)
-
-    def pack_media_ref(self, media_ref):
-        citation_list = self.get_citation_list(media_ref)
-        note_list = self.get_note_list(media_ref)
-        attribute_list = self.get_attribute_list(media_ref)
-        if ((media_ref.x1 == media_ref.y1 == media_ref.x2 == media_ref.y2 == -1) or
-            (media_ref.x1 == media_ref.y1 == media_ref.x2 == media_ref.y2 == 0)):
-            role = None
-        else:
-            role = (media_ref.x1, media_ref.y1, media_ref.x2, media_ref.y2)
-        return (media_ref.private, citation_list, note_list, attribute_list, 
-                media_ref.ref_object.handle, role)
-
-    def pack_repository_ref(self, repo_ref):
-        note_list = self.get_note_list(repo_ref)
-        return (note_list, 
-                repo_ref.ref_object.handle,
-                repo_ref.call_number, 
-                tuple(repo_ref.source_media_type),
-                repo_ref.private)
-
-    def pack_place_ref(self, place_ref):
-        date = self.get_date(place_ref)
-        return (place_ref.ref_object.handle, date)
-
-    def pack_media_ref(self, media_ref):
-        note_list = self.get_note_list(media_ref)
-        attribute_list = self.get_attribute_list(media_ref)
-        citation_list = self.get_citation_list(media_ref)
-        return (media_ref.private, citation_list, note_list, attribute_list, 
-                media_ref.ref_object.handle, (media_ref.x1,
-                                              media_ref.y1,
-                                              media_ref.x2,
-                                              media_ref.y2))
-    
-    def pack_event_ref(self, event_ref):
-        note_list = self.get_note_list(event_ref)
-        attribute_list = self.get_attribute_list(event_ref)
-        return (event_ref.private, note_list, attribute_list, 
-                event_ref.ref_object.handle, tuple(event_ref.role_type))
-
-    def pack_citation(self, citation):
-        handle = citation.handle
-        gid = citation.gramps_id
-        date = self.get_date(citation)
-        page = citation.page
-        confidence = citation.confidence
-        source_handle = citation.source.handle
-        note_list = self.get_note_list(citation)
-        media_list = self.get_media_list(citation)
-        attribute_list = self.get_citation_attribute_list(citation)
-        changed = totime(citation.last_changed)
-        private = citation.private
-        tag_list = self.get_tag_list(citation)
-        return (handle, gid, date, page, confidence, source_handle,
-                note_list, media_list, attribute_list, changed, tag_list, 
-                private) 
-
-    def pack_address(self, address, with_parish):
-        citation_list = self.get_citation_list(address)
-        date = self.get_date(address)
-        note_list = self.get_note_list(address)
-        locations = address.location_set.all().order_by("order")
-        if len(locations) > 0:
-            location = self.pack_location(locations[0], with_parish)
-        else:
-            if with_parish:
-                location = (("", "", "", "", "", "", ""), "")
-            else:
-                location = ("", "", "", "", "", "", "")
-        return (address.private, citation_list, note_list, date, location)
-
-    def pack_lds(self, lds):
-        citation_list = self.get_citation_list(lds)
-        note_list = self.get_note_list(lds)
-        date = self.get_date(lds)
-        if lds.famc:
-            famc = lds.famc.handle
-        else:
-            famc = None
-        place_handle = self.get_place_handle(lds)
-        return (citation_list, note_list, date, lds.lds_type[0], place_handle,
-                famc, lds.temple, lds.status[0], lds.private)
-
-    def pack_source(self, source):
-        note_list = self.get_note_list(source)
-        media_list = self.get_media_list(source)
-        reporef_list = self.get_repository_ref_list(source)
-        attribute_list = self.get_source_attribute_list(source)
-        tag_list = self.get_tag_list(source)
-        return (source.handle, source.gramps_id, source.title,
-                source.author, source.pubinfo,
-                note_list,
-                media_list,
-                source.abbrev,
-                totime(last_changed), attribute_list,
-                reporef_list,
-                tag_list,
-                source.private)
-
-    def pack_name(self, name):
-        citation_list = self.get_citation_list(name)
-        note_list = self.get_note_list(name)
-        date = self.get_date(name)
-        return (name.private, citation_list, note_list, date,
-                name.first_name, name.make_surname_list(), name.suffix,
-                name.title, tuple(name.name_type), 
-                name.group_as, name.sort_as.val, 
-                name.display_as.val, name.call, name.nick, 
-                name.famnick)
-
-    def pack_location(self, loc, with_parish):
-        if with_parish:
-            return ((loc.street, loc.locality, loc.city, loc.county, loc.state, loc.country, 
-                     loc.postal, loc.phone), loc.parish)
-        else:
-            return (loc.street, loc.locality, loc.city, loc.county, loc.state, loc.country, 
-                    loc.postal, loc.phone)
-
-    def pack_url(self, url):
-        return  (url.private, url.path, url.desc, tuple(url.url_type))
-
-    def pack_attribute(self, attribute):
-        citation_list = self.get_citation_list(attribute)
-        note_list = self.get_note_list(attribute)
-        return (attribute.private, 
-                citation_list, 
-                note_list, 
-                tuple(attribute.attribute_type), 
-                attribute.value)
-
-
-    ## Export lists:
-    
-    def add_child_ref_list(self, obj, ref_list):
-        ## Currently, only Family references children
-        for child_data in ref_list:
-            self.add_child_ref(obj, child_data)
-    
-    def add_citation_list(self, obj, citation_list):
-        for citation_handle in citation_list:
-            self.add_citation_ref(obj, citation_handle)
-    
-    def add_event_ref_list(self, obj, event_ref_list):
-        for event_ref in event_ref_list:
-            self.add_event_ref(obj, event_ref)
-    
-    def add_surname_list(self, name, surname_list):
-        order = 1
-        for data in surname_list:
-            (surname_text, prefix, primary, origin_type,
-             connector) = data
-            surname = models.Surname()
-            surname.surname = surname_text
-            surname.prefix = prefix
-            surname.primary = primary
-            surname.name_origin_type = models.get_type(models.NameOriginType, 
-                                                       origin_type)
-            surname.connector = connector
-            surname.name = name
-            surname.order = order
-            surname.save() 
-            order += 1
-
-    def add_note_list(self, obj, note_list):
-        for handle in note_list:
-            # Just the handle
-            try:
-                note = models.Note.objects.get(handle=handle)
-                self.add_note_ref(obj, note)
-            except:
-                print(("ERROR: Note does not exist: '%s'" % 
-                                      str(handle)), file=sys.stderr)
-    
-    def add_alternate_name_list(self, person, alternate_names):
-        for name in alternate_names:
-            if name:
-                self.add_name(person, name, False)
-    
-    def add_parent_family_list(self, person, parent_family_list):
-        for parent_family_data in parent_family_list:
-            self.add_parent_family(person, parent_family_data)
-    
-    def add_media_ref_list(self, person, media_list):
-        for media_data in media_list:
-            self.add_media_ref(person, media_data)
-    
-    def add_attribute_list(self, obj, attribute_list):
-        for attribute_data in attribute_list:
-            self.add_attribute(obj, attribute_data)
-    
-    def add_tag_list(self, obj, tag_list):
-        for tag_handle in tag_list:
-            try:
-                tag = models.Tag.objects.get(handle=tag_handle)
-            except:
-                print(("ERROR: Tag does not exist: '%s'" % 
-                                      str(tag_handle)), file=sys.stderr)
-            obj.tags.add(tag)
-    
-    def add_url_list(self, field, obj, url_list):
-        if not url_list: return None
-        count = 1
-        for url_data in url_list:
-            self.add_url(field, obj, url_data, count) 
-            count += 1
-            
-    def add_person_ref_list(self, obj, person_ref_list):
-        for person_ref_data in person_ref_list:
-            self.add_person_ref(obj, person_ref_data)
-    
-    def add_address_list(self, field, obj, address_list):
-        count = 1
-        for address_data in address_list:
-            self.add_address(field, obj, address_data, count)
-            count += 1
-    
-    def add_lds_list(self, field, obj, lds_ord_list):
-        count = 1
-        for ldsord in lds_ord_list:
-            lds = self.add_lds(field, obj, ldsord, count)
-            #obj.lds_list.add(lds)
-            #obj.save()
-            count += 1
-    
-    def add_repository_ref_list(self, obj, reporef_list):
-        for data in reporef_list:
-            self.add_repository_ref(obj, data)
-    
-    def add_place_ref_list(self, obj, placeref_list):
-        for data in placeref_list:
-            self.add_place_ref(obj, data)
-    
-    def add_family_ref_list(self, person, family_list):
-        for family_handle in family_list:
-            self.add_family_ref(person, family_handle) 
-    
-    def add_alt_name_list(self, place, alt_name_list):
-        print("FIXME: add alt_name_list!", alt_name_list)
-
-    ## Export reference objects:
-
-    def add_person_ref_default(self, obj, person, private=False, desc=None):
-        count = person.references.count()
-        person_ref = models.PersonRef(referenced_by=obj,
-                                  ref_object=person,
-                                  private=private,
-                                  order=count + 1,
-                                  description=desc)
-        person_ref.save()
-
-    def add_person_ref(self, obj, person_ref_data):
-        (private, 
-         citation_list,
-         note_list,
-         handle,
-         desc) = person_ref_data
-        try:
-            person = models.Person.objects.get(handle=handle)
-        except:
-            print(("ERROR: Person does not exist: '%s'" % 
-                                  str(handle)), file=sys.stderr)
-            return
-            
-        count = person.references.count()
-        person_ref = models.PersonRef(referenced_by=obj,
-                                  ref_object=person,
-                                  private=private,
-                                  order=count + 1,
-                                  description=desc)
-        person_ref.save()
-        self.add_note_list(person_ref, note_list)
-        self.add_citation_list(person_ref, citation_list)
-    
-    def add_note_ref(self, obj, note):
-        count = note.references.count()
-        note_ref = models.NoteRef(referenced_by=obj, 
-                              ref_object=note,
-                              private=False,
-                              order=count + 1)
-        note_ref.save()
-    
-    def add_media_ref_default(self, obj, media, private=False, role=None):
-        count = media.references.count()
-        if not role:
-            role = (0,0,0,0)
-        media_ref = models.MediaRef(referenced_by=obj, 
-                                ref_object=media,
-                                x1=role[0],
-                                y1=role[1],
-                                x2=role[2],
-                                y2=role[3],
-                                private=private,
-                                order=count + 1)
-        media_ref.save()
-
-    def add_media_ref(self, obj, media_ref_data):
-        (private, citation_list, note_list, attribute_list, 
-         ref, role) = media_ref_data
-        try:
-            media = models.Media.objects.get(handle=ref)
-        except:
-            print(("ERROR: Media does not exist: '%s'" % 
-                                  str(ref)), file=sys.stderr)
-            return
-        count = media.references.count()
-        if not role:
-            role = (0,0,0,0)
-        media_ref = models.MediaRef(referenced_by=obj, 
-                                ref_object=media,
-                                x1=role[0],
-                                y1=role[1],
-                                x2=role[2],
-                                y2=role[3],
-                                private=private,
-                                order=count + 1)
-        media_ref.save()
-        self.add_note_list(media_ref, note_list)
-        self.add_attribute_list(media_ref, attribute_list)
-        self.add_citation_list(media_ref, citation_list)
-    
-    def add_citation_ref_default(self, obj, citation, private=False):
-        object_type = ContentType.objects.get_for_model(obj)
-        count = models.CitationRef.objects.filter(object_id=obj.id,object_type=object_type).count()
-        citation_ref = models.CitationRef(private=private,
-                                          referenced_by=obj,
-                                          citation=citation,
-                                          order=count + 1)
-        citation_ref.save()
-
-    def add_citation_ref(self, obj, handle):
-        try:
-            citation = models.Citation.objects.get(handle=handle)
-        except:
-            print(("ERROR: Citation does not exist: '%s'" % 
-                                  str(handle)), file=sys.stderr)
-            return
-
-        object_type = ContentType.objects.get_for_model(obj)
-        count = models.CitationRef.objects.filter(object_id=obj.id,object_type=object_type).count()
-        citation_ref = models.CitationRef(private=False,
-                                          referenced_by=obj,
-                                          citation=citation,
-                                          order=count + 1)
-        citation_ref.save()
-
-    def add_citation(self, citation_data):
-        (handle, gid, date, page, confidence, source_handle, note_list,
-         media_list, attribute_list, changed, tag_list, private) = citation_data
-        citation = models.Citation(
-            handle=handle,
-            gramps_id=gid,
-            private=private, 
-            last_changed=todate(changed),
-            confidence=confidence, 
-            page=page)
-        citation.save(save_cache=False)
-
-    def add_citation_detail(self, citation_data):
-        (handle, gid, date, page, confidence, source_handle, note_list,
-         media_list, attribute_list, change, tag_list, private) = citation_data
-        try:
-            citation = models.Citation.objects.get(handle=handle)
-        except:
-            print(("ERROR: Citation does not exist: '%s'" % 
-                                  str(handle)), file=sys.stderr)
-            return
-        try:
-            source = models.Source.objects.get(handle=source_handle)
-        except:
-            print(("ERROR: Source does not exist: '%s'" % 
-                                  str(source_handle)), file=sys.stderr)
-            return
-        citation.source = source
-        self.add_date(citation, date)
-        citation.save(save_cache=False)
-        self.add_note_list(citation, note_list) 
-        self.add_media_ref_list(citation, media_list) 
-        self.add_citation_attribute_list(citation, attribute_list)
-        self.add_tag_list(citation, tag_list)
-        citation.save_cache()
-
-    def add_child_ref_default(self, obj, child, frel=1, mrel=1, private=False):
-        object_type = ContentType.objects.get_for_model(obj) # obj is family
-        count = models.ChildRef.objects.filter(object_id=obj.id,object_type=object_type).count()
-        child_ref = models.ChildRef(private=private,
-                                referenced_by=obj,
-                                ref_object=child,
-                                order=count + 1,
-                                father_rel_type=models.get_type(models.ChildRefType, frel),  # birth
-                                mother_rel_type=models.get_type(models.ChildRefType, mrel))
-        child_ref.save()
-
-    def add_child_ref(self, obj, data):
-        (private, citation_list, note_list, ref, frel, mrel) = data
-        try:
-            child = models.Person.objects.get(handle=ref)
-        except:
-            print(("ERROR: Person does not exist: '%s'" % 
-                                  str(ref)), file=sys.stderr)
-            return
-        object_type = ContentType.objects.get_for_model(obj)
-        count = models.ChildRef.objects.filter(object_id=obj.id,object_type=object_type).count()
-        child_ref = models.ChildRef(private=private,
-                                referenced_by=obj,
-                                ref_object=child,
-                                order=count + 1,
-                                father_rel_type=models.get_type(models.ChildRefType, frel),
-                                mother_rel_type=models.get_type(models.ChildRefType, mrel))
-        child_ref.save()
-        self.add_citation_list(child_ref, citation_list)
-        self.add_note_list(child_ref, note_list)
-    
-    def add_event_ref_default(self, obj, event, private=False, role=models.EventRoleType._DEFAULT):
-        object_type = ContentType.objects.get_for_model(obj)
-        count = models.EventRef.objects.filter(object_id=obj.id,object_type=object_type).count()
-        event_ref = models.EventRef(private=private,
-                                referenced_by=obj,
-                                ref_object=event,
-                                order=count + 1,
-                                role_type = models.get_type(models.EventRoleType, role))
-        event_ref.save()
-
-    def add_event_ref(self, obj, event_data):
-        (private, note_list, attribute_list, ref, role) = event_data
-        try:
-            event = models.Event.objects.get(handle=ref)
-        except:
-            print(("ERROR: Event does not exist: '%s'" % 
-                                  str(ref)), file=sys.stderr)
-            return
-        object_type = ContentType.objects.get_for_model(obj)
-        count = models.EventRef.objects.filter(object_id=obj.id,object_type=object_type).count()
-        event_ref = models.EventRef(private=private,
-                                referenced_by=obj,
-                                ref_object=event,
-                                order=count + 1,
-                                role_type = models.get_type(models.EventRoleType, role))
-        event_ref.save()
-        self.add_note_list(event_ref, note_list)
-        self.add_attribute_list(event_ref, attribute_list)
-
-    def add_repository_ref_default(self, obj, repository, private=False, call_number="", 
-                                   source_media_type=models.SourceMediaType._DEFAULT):
-        object_type = ContentType.objects.get_for_model(obj)
-        count = models.RepositoryRef.objects.filter(object_id=obj.id,object_type=object_type).count()
-        repos_ref = models.RepositoryRef(private=private,
-                                         referenced_by=obj,
-                                         call_number=call_number,
-                                         source_media_type=models.get_type(models.SourceMediaType,
-                                                                           source_media_type),
-                                         ref_object=repository,
-                                         order=count + 1)
-        repos_ref.save()
-    
-    def add_repository_ref(self, obj, reporef_data):
-        (note_list, 
-         ref,
-         call_number, 
-         source_media_type,
-         private) = reporef_data
-        try:
-            repository = models.Repository.objects.get(handle=ref)
-        except:
-            print(("ERROR: Repository does not exist: '%s'" % 
-                                  str(ref)), file=sys.stderr)
-            return
-        object_type = ContentType.objects.get_for_model(obj)
-        count = models.RepositoryRef.objects.filter(object_id=obj.id,object_type=object_type).count()
-        repos_ref = models.RepositoryRef(private=private,
-                                     referenced_by=obj,
-                                     call_number=call_number,
-                                     source_media_type=models.get_type(models.SourceMediaType,
-                                                                source_media_type),
-                                     ref_object=repository,
-                                     order=count + 1)
-        repos_ref.save()
-        self.add_note_list(repos_ref, note_list)
-    
-    def add_family_ref(self, obj, handle):
-        try:
-            family = models.Family.objects.get(handle=handle)
-        except:
-            print(("ERROR: Family does not exist: '%s'" % 
-                                  str(handle)), file=sys.stderr)
-            return
-        #obj.families.add(family)
-        pfo = models.MyFamilies(person=obj, family=family,
-                                order=len(models.MyFamilies.objects.filter(person=obj)) + 1)
-        pfo.save()
-        obj.save()
-    
-    ## Export individual objects:
-    
-    def add_source_attribute_list(self, source, attribute_list):
-        ## FIXME: dict to list
-        count = 1
-        #for key in datamap_dict:
-        #    value = datamap_dict[key]
-        #    datamap = models.SourceDatamap(key=key, value=value, order=count)
-        #    datamap.source = source
-        #    datamap.save()
-        #    count += 1
-    
-    def add_citation_attribute_list(self, citation, attribute_list):
-        ## FIXME: dict to list
-        count = 1
-        #for key in datamap_dict:
-        #    value = datamap_dict[key]
-        #    datamap = models.CitationDatamap(key=key, value=value, order=count)
-        #    datamap.citation = citation
-        #    datamap.save()
-        #    count += 1
-    
-    def add_lds(self, field, obj, data, order):
-        (lcitation_list, lnote_list, date, type, place_handle,
-         famc_handle, temple, status, private) = data
-        if place_handle:
-            try:
-                place = models.Place.objects.get(handle=place_handle)
-            except:
-                print(("ERROR: Place does not exist: '%s'" % 
-                                      str(place_handle)), file=sys.stderr)
-                place = None
-        else:
-            place = None
-        if famc_handle:
-            try:
-                famc = models.Family.objects.get(handle=famc_handle)
-            except:
-                print(("ERROR: Family does not exist: '%s'" % 
-                                      str(famc_handle)), file=sys.stderr)
-                famc = None
-        else:
-            famc = None
-        lds = models.Lds(lds_type = models.get_type(models.LdsType, type),
-                     temple=temple, 
-                     place=place,
-                     famc=famc,
-                     order=order,
-                     status = models.get_type(models.LdsStatus, status),
-                     private=private)
-        self.add_date(lds, date)
-        lds.save()
-        self.add_note_list(lds, lnote_list)
-        self.add_citation_list(lds, lcitation_list)
-        if field == "person":
-            lds.person = obj
-        elif field == "family":
-            lds.family = obj
-        else:
-            raise AttributeError("invalid field '%s' to attach lds" %
-                                 str(field))
-        lds.save()
-        return lds
-    
-    def add_address(self, field, obj, address_data, order):
-        (private, acitation_list, anote_list, date, location) = address_data
-        address = models.Address(private=private, order=order)
-        self.add_date(address, date)
-        address.save()
-        self.add_location("address", address, location, 1)
-        self.add_note_list(address, anote_list) 
-        self.add_citation_list(address, acitation_list)
-        if field == "person":
-            address.person = obj
-        elif field == "repository":
-            address.repository = obj
-        else:
-            raise AttributeError("invalid field '%s' to attach address" %
-                                 str(field))
-        address.save()
-        #obj.save()
-        #obj.addresses.add(address)
-        #obj.save()
-    
-    def add_attribute(self, obj, attribute_data):
-        (private, citation_list, note_list, the_type, value) = attribute_data
-        attribute_type = models.get_type(models.AttributeType, the_type)
-        attribute = models.Attribute(private=private,
-                                 attribute_of=obj,
-                                 attribute_type=attribute_type,
-                                 value=value)
-        attribute.save()
-        self.add_citation_list(attribute, citation_list)
-        self.add_note_list(attribute, note_list)
-        #obj.attributes.add(attribute)
-        #obj.save()
-    
-    def add_url(self, field, obj, url_data, order):
-        (private, path, desc, type) = url_data
-        url = models.Url(private=private,
-                     path=path,
-                     desc=desc,
-                     order=order,
-                     url_type=models.get_type(models.UrlType, type))
-        if field == "person":
-            url.person = obj
-        elif field == "repository":
-            url.repository = obj
-        elif field == "place":
-            url.place = obj
-        else:
-            raise AttributeError("invalid field '%s' to attach to url" %
-                                 str(field))
-        url.save()
-        #obj.url_list.add(url)
-        #obj.save()
-    
-    def add_place_ref_default(self, obj, place, date=None):
-        count = place.references.count()
-        object_type = ContentType.objects.get_for_model(obj)
-        count = models.PlaceRef.objects.filter(object_id=obj.id,
-                                               object_type=object_type).count()
-        place_ref = models.PlaceRef(referenced_by=obj,
-                                    ref_object=place,
-                                    order=count + 1)
-        self.add_date(obj, date)
-        place_ref.save()
-    
-    def add_place_ref(self, obj, data):
-        place_handle, date = data
-        if place_handle:
-            try:
-                place = models.Place.objects.get(handle=place_handle)
-            except:
-                print(("ERROR: Place does not exist: '%s'" % str(place_handle)), file=sys.stderr)
-                #from gramps.gen.utils.debug import format_exception
-                #print("".join(format_exception()), file=sys.stderr)
-                return
-        object_type = ContentType.objects.get_for_model(obj)
-        count = models.PlaceRef.objects.filter(object_id=obj.id,object_type=object_type).count()
-        place_ref = models.PlaceRef(referenced_by=obj, ref_object=place, order=count + 1)
-        place_ref.save()
-        self.add_date(place_ref, date)
-
-    def add_parent_family(self, person, parent_family_handle):
-        try:
-            family = models.Family.objects.get(handle=parent_family_handle)
-        except:
-            print(("ERROR: Family does not exist: '%s'" % 
-                                  str(parent_family_handle)), file=sys.stderr)
-            return
-        #person.parent_families.add(family)
-        pfo = models.MyParentFamilies(
-            person=person, 
-            family=family,
-            order=len(models.MyParentFamilies.objects.filter(person=person)) + 1)
-        pfo.save()
-        person.save()
-    
-    def add_date(self, obj, date):
-        if date is None: 
-            (calendar, modifier, quality, text, sortval, newyear) = \
-                (0, 0, 0, "", 0, 0)
-            day1, month1, year1, slash1 = 0, 0, 0, 0
-            day2, month2, year2, slash2 = 0, 0, 0, 0
-        else:
-            (calendar, modifier, quality, dateval, text, sortval, newyear) = date
-            if len(dateval) == 4:
-                day1, month1, year1, slash1 = dateval
-                day2, month2, year2, slash2 = 0, 0, 0, 0
-            elif len(dateval) == 8:
-                day1, month1, year1, slash1, day2, month2, year2, slash2 = dateval
-            else:
-                raise AttributeError("ERROR: dateval format '%s'" % str(dateval))
-        obj.calendar = calendar
-        obj.modifier = modifier
-        obj.quality = quality
-        obj.text = text
-        obj.sortval = sortval
-        obj.newyear = newyear
-        obj.day1 = day1
-        obj.month1 = month1
-        obj.year1 = year1
-        obj.slash1 = slash1
-        obj.day2 = day2
-        obj.month2 = month2
-        obj.year2 = year2
-        obj.slash2 = slash2
-    
-    def add_name(self, person, data, preferred):
-        if data:
-            (private, citation_list, note_list, date,
-             first_name, surname_list, suffix, title,
-             name_type, group_as, sort_as, 
-             display_as, call, nick, famnick) = data
-    
-            count = person.name_set.count()
-            name = models.Name()
-            name.order = count + 1
-            name.preferred = preferred
-            name.private = private
-            name.first_name = first_name
-            name.suffix = suffix
-            name.title = title
-            name.name_type = models.get_type(models.NameType, name_type)
-            name.group_as = group_as
-            name.sort_as = models.get_type(models.NameFormatType, sort_as)
-            name.display_as = models.get_type(models.NameFormatType, display_as)
-            name.call = call
-            name.nick = nick
-            name.famnick = famnick
-            # we know person exists
-            # needs to have an ID for key
-            name.person = person
-            self.add_date(name, date) 
-            name.save()
-            self.add_surname_list(name, surname_list)
-            self.add_note_list(name, note_list)
-            self.add_citation_list(name, citation_list)
-            #person.save()
-           
-    ## Export primary objects:
-    
-    def add_person(self, data):
-        # Unpack from the BSDDB:
-        (handle,        #  0
-         gid,          #  1
-         gender,             #  2
-         primary_name,       #  3
-         alternate_names,    #  4
-         death_ref_index,    #  5
-         birth_ref_index,    #  6
-         event_ref_list,     #  7
-         family_list,        #  8
-         parent_family_list, #  9
-         media_list,         # 10
-         address_list,       # 11
-         attribute_list,     # 12
-         url_list,               # 13
-         lds_ord_list,       # 14
-         pcitation_list,       # 15
-         pnote_list,         # 16
-         change,             # 17
-         tag_list,             # 18
-         private,           # 19
-         person_ref_list,    # 20
-         ) = data
-    
-        person = models.Person(handle=handle,
-                            gramps_id=gid,
-                            last_changed=todate(change),
-                            private=private,
-                            gender_type=models.get_type(models.GenderType, gender))
-        person.save(save_cache=False)
-
-    def add_person_detail(self, data):
-        # Unpack from the BSDDB:
-        (handle,        #  0
-         gid,          #  1
-         gender,             #  2
-         primary_name,       #  3
-         alternate_names,    #  4
-         death_ref_index,    #  5
-         birth_ref_index,    #  6
-         event_ref_list,     #  7
-         family_list,        #  8
-         parent_family_list, #  9
-         media_list,         # 10
-         address_list,       # 11
-         attribute_list,     # 12
-         url_list,               # 13
-         lds_ord_list,       # 14
-         pcitation_list,       # 15
-         pnote_list,         # 16
-         change,             # 17
-         tag_list,             # 18
-         private,           # 19
-         person_ref_list,    # 20
-         ) = data
-    
-        try:
-            person = models.Person.objects.get(handle=handle)
-        except:
-            print(("ERROR: Person does not exist: '%s'" % 
-                                  str(handle)), file=sys.stderr)
-            return
-        if primary_name:
-            self.add_name(person, primary_name, True)
-        self.add_alternate_name_list(person, alternate_names)
-        self.add_event_ref_list(person, event_ref_list)
-        self.add_family_ref_list(person, family_list) 
-        self.add_parent_family_list(person, parent_family_list)
-        self.add_media_ref_list(person, media_list)
-        self.add_note_list(person, pnote_list)
-        self.add_attribute_list(person, attribute_list)
-        self.add_url_list("person", person, url_list) 
-        self.add_person_ref_list(person, person_ref_list)
-        self.add_citation_list(person, pcitation_list)
-        self.add_address_list("person", person, address_list)
-        self.add_lds_list("person", person, lds_ord_list)
-        self.add_tag_list(person, tag_list)
-        # set person.birth and birth.death to correct events:
-
-        obj_type = ContentType.objects.get_for_model(person)
-        events = models.EventRef.objects.filter(
-            object_id=person.id, 
-            object_type=obj_type, 
-            ref_object__event_type__val=models.EventType.BIRTH).order_by("order")
-
-        all_events = self.get_event_ref_list(person)
-        if events:
-            person.birth = events[0].ref_object
-            person.birth_ref_index = lookup_role_index(models.EventType.BIRTH, all_events)
-
-        events = models.EventRef.objects.filter(
-            object_id=person.id, 
-            object_type=obj_type, 
-            ref_object__event_type__val=models.EventType.DEATH).order_by("order")
-        if events:
-            person.death = events[0].ref_object
-            person.death_ref_index = lookup_role_index(models.EventType.DEATH, all_events)
-        person.save()
-        return person
-
-    def save_note_markup(self, note, markup_list):
-        # delete any prexisting markup:
-        models.Markup.objects.filter(note=note).delete()
-        count = 1
-        for markup in markup_list:
-            markup_code, value, start_stop_list = markup
-            m = models.Markup(
-                note=note, 
-                order=count, 
-                styled_text_tag_type=models.get_type(models.StyledTextTagType,
-                                                     markup_code, 
-                                                     get_or_create=False),
-                string=value,
-                start_stop_list=str(start_stop_list))
-            m.save()
-    
-    def add_note(self, data):
-        # Unpack from the BSDDB:
-        (handle, gid, styled_text, format, note_type,
-         change, tag_list, private) = data
-        text, markup_list = styled_text
-        n = models.Note(handle=handle,
-                        gramps_id=gid,
-                        last_changed=todate(change),
-                        private=private,
-                        preformatted=format,
-                        text=text,
-                        note_type=models.get_type(models.NoteType, note_type))
-        n.save(save_cache=False)
-        self.save_note_markup(n, markup_list)
-    
-    def add_note_detail(self, data):
-        # Unpack from the BSDDB:
-        (handle, gid, styled_text, format, note_type,
-         change, tag_list, private) = data
-        note = models.Note.objects.get(handle=handle)
-        note.save(save_cache=False)
-        self.add_tag_list(note, tag_list)
-        note.save_cache()
-
-    def add_family(self, data):
-        # Unpack from the BSDDB:
-        (handle, gid, father_handle, mother_handle,
-         child_ref_list, the_type, event_ref_list, media_list,
-         attribute_list, lds_seal_list, citation_list, note_list,
-         change, tag_list, private) = data
-    
-        family = models.Family(handle=handle, gramps_id=gid, 
-                               family_rel_type = models.get_type(models.FamilyRelType, the_type),
-                               last_changed=todate(change), 
-                               private=private)
-        family.save(save_cache=False)
-
-    def add_family_detail(self, data):
-        # Unpack from the BSDDB:
-        (handle, gid, father_handle, mother_handle,
-         child_ref_list, the_type, event_ref_list, media_list,
-         attribute_list, lds_seal_list, citation_list, note_list,
-         change, tag_list, private) = data
-    
-        try:
-            family = models.Family.objects.get(handle=handle)
-        except:
-            print(("ERROR: Family does not exist: '%s'" % 
-                                  str(handle)), file=sys.stderr)
-            return
-        # father_handle and/or mother_handle can be None
-        if father_handle:
-            try:
-                family.father = models.Person.objects.get(handle=father_handle)
-            except:
-                print(("ERROR: Father does not exist: '%s'" % 
-                                      str(father_handle)), file=sys.stderr)
-                family.father = None
-        if mother_handle:
-            try:
-                family.mother = models.Person.objects.get(handle=mother_handle)
-            except:
-                print(("ERROR: Mother does not exist: '%s'" % 
-                                      str(mother_handle)), file=sys.stderr)
-                family.mother = None
-        family.save(save_cache=False)
-        self.add_child_ref_list(family, child_ref_list)
-        self.add_note_list(family, note_list)
-        self.add_attribute_list(family, attribute_list)
-        self.add_citation_list(family, citation_list)
-        self.add_media_ref_list(family, media_list)
-        self.add_event_ref_list(family, event_ref_list)
-        self.add_lds_list("family", family, lds_seal_list)
-        self.add_tag_list(family, tag_list)
-        family.save_cache()
-        
-    def add_source(self, data):
-        (handle, gid, title,
-         author, pubinfo,
-         note_list,
-         media_list,
-         abbrev,
-         change, attribute_list,
-         reporef_list,
-         tag_list,
-         private) = data
-        source = models.Source(handle=handle, gramps_id=gid, title=title,
-                               author=author, pubinfo=pubinfo, abbrev=abbrev,
-                               last_changed=todate(change), private=private)
-        source.save(save_cache=False)
-
-    def add_source_detail(self, data):
-        (handle, gid, title,
-         author, pubinfo,
-         note_list,
-         media_list,
-         abbrev,
-         change, attribute_list,
-         reporef_list,
-         tag_list,
-         private) = data
-        try:
-            source = models.Source.objects.get(handle=handle)
-        except:
-            print(("ERROR: Source does not exist: '%s'" % 
-                                  str(handle)), file=sys.stderr)
-            return
-        source.save(save_cache=False)
-        self.add_note_list(source, note_list) 
-        self.add_media_ref_list(source, media_list)
-        self.add_source_attribute_list(source, attribute_list)
-        self.add_repository_ref_list(source, reporef_list)
-        self.add_tag_list(source, tag_list)
-        source.save_cache()
-    
-    def add_repository(self, data):
-        (handle, gid, the_type, name, note_list,
-         address_list, url_list, change, tag_list, private) = data
-    
-        repository = models.Repository(handle=handle,
-                                       gramps_id=gid,
-                                       last_changed=todate(change), 
-                                       private=private,
-                                       repository_type=models.get_type(models.RepositoryType, the_type),
-                                       name=name)
-        repository.save(save_cache=False)
-
-    def add_repository_detail(self, data):
-        (handle, gid, the_type, name, note_list,
-         address_list, url_list, change, tag_list, private) = data
-        try:
-            repository = models.Repository.objects.get(handle=handle)
-        except:
-            print(("ERROR: Repository does not exist: '%s'" % 
-                                  str(handle)), file=sys.stderr)
-            return
-        repository.save(save_cache=False)
-        self.add_note_list(repository, note_list)
-        self.add_url_list("repository", repository, url_list)
-        self.add_address_list("repository", repository, address_list)
-        self.add_tag_list(repository, tag_list)
-        repository.save_cache()
-    
-    def add_location(self, field, obj, location_data, order):
-        # location now has 8 items
-        # street, locality, city, county, state,
-        # country, postal, phone, parish
-
-        if location_data == None: return
-        if len(location_data) == 8:
-            (street, locality, city, county, state, country, postal, phone) = location_data
-            parish = None
-        elif len(location_data) == 2:
-            ((street, locality, city, county, state, country, postal, phone), parish) = location_data
-        else:
-            print(("ERROR: unknown location: '%s'" % 
-                                  str(location_data)), file=sys.stderr)
-            (street, locality, city, county, state, country, postal, phone, parish) = \
-                ("", "", "", "", "", "", "", "", "")
-        location = models.Location(street = street,
-                                   locality = locality,
-                               city = city,
-                               county = county,
-                               state = state,
-                               country = country,
-                               postal = postal,
-                               phone = phone,
-                               parish = parish,
-                               order = order)
-        if field == "address":
-            location.address = obj
-        elif field == "place":
-            location.place = obj
-        else:
-            raise AttributeError("invalid field '%s' to attach to location" %
-                                 str(field))
-        location.save()
-        #obj.locations.add(location)
-        #obj.save()
-    
-    def add_place(self, data):
-        ## ('cef246c95c132bcf6a0255d4d17', 'P0036', 'Santa Clara Co., CA, USA', '', '', [('cef243fb5634559442323368f63', None)], 'Santa Clara Co.', [], (3, ''), '', [], [], [], [], [], 1422124781, [], False)
-        (handle, gid, title, long, lat,
-         place_ref_list,
-         name,
-         alt_name_list,
-         place_type,
-         code,
-         alt_location_list,
-         url_list,
-         media_list,
-         citation_list,
-         note_list,
-         change, 
-         tag_list,
-         private) = data
-        place = models.Place(
-            handle=handle, 
-            gramps_id=gid, 
-            title=title,
-            long=long, 
-            lat=lat, 
-            name=name,
-            place_type=models.get_type(models.PlaceType, place_type), 
-            code=code, 
-            last_changed=todate(change),
-            private=private)
-        try:
-            place.save(save_cache=False)
-        except:
-            print("FIXME: error in saving place")
-
-    def add_place_detail(self, data):
-        (handle, gid, title, long, lat,
-         place_ref_list,
-         name,
-         alt_name_list,
-         place_type,
-         code,
-         alt_location_list,
-         url_list,
-         media_list,
-         citation_list,
-         note_list,
-         change, 
-         tag_list,
-         private) = data
-        try:
-            place = models.Place.objects.get(handle=handle)
-        except:
-            print(("ERROR: Place does not exist: '%s'" % 
-                                  str(handle)), file=sys.stderr)
-            return
-        place.save(save_cache=False)
-        self.add_url_list("place", place, url_list)
-        self.add_media_ref_list(place, media_list)
-        self.add_citation_list(place, citation_list)
-        self.add_note_list(place, note_list) 
-        self.add_tag_list(place, tag_list)
-        self.add_place_ref_list(place, place_ref_list)
-        self.add_alt_name_list(place, alt_name_list)
-        count = 1
-        for loc_data in alt_location_list:
-            self.add_location("place", place, loc_data, count)
-            count + 1
-        place.save_cache()
-
-    def add_tag(self, data):
-        (handle,
-         name,
-         color,
-         priority,
-         change) = data
-        tag = models.Tag(handle=handle,
-                         gramps_id=create_id(),
-                         name=name,
-                         color=color,
-                         priority=priority,
-                         last_changed=todate(change))
-        tag.save(save_cache=False)
-    
-    def add_tag_detail(self, data):
-        (handle,
-         name,
-         color,
-         priority,
-         change) = data
-        tag = models.Tag.objects.get(handle=handle)
-        tag.save()
-
-    def add_media(self, data):
-        (handle, gid, path, mime, desc,
-         checksum,
-         attribute_list,
-         citation_list,
-         note_list,
-         change,
-         date,
-         tag_list,
-         private) = data
-        media = models.Media(handle=handle, gramps_id=gid,
-                             path=path, mime=mime, checksum=checksum,
-                             desc=desc, last_changed=todate(change),
-                             private=private)
-        self.add_date(media, date)
-        media.save(save_cache=False)
-    
-    def add_media_detail(self, data):
-        (handle, gid, path, mime, desc,
-         checksum,
-         attribute_list,
-         citation_list,
-         note_list,
-         change,
-         date,
-         tag_list,
-         private) = data
-        try:
-            media = models.Media.objects.get(handle=handle)
-        except:
-            print(("ERROR: Media does not exist: '%s'" % 
-                                  str(handle)), file=sys.stderr)
-            return
-        media.save(save_cache=False)
-        self.add_note_list(media, note_list) 
-        self.add_citation_list(media, citation_list)
-        self.add_attribute_list(media, attribute_list)
-        self.add_tag_list(media, tag_list)
-        media.save_cache()
-    
-    def add_event(self, data):
-        (handle, gid, the_type, date, description, place_handle, 
-         citation_list, note_list, media_list, attribute_list,
-         change, tag_list, private) = data
-        event = models.Event(handle=handle,
-                             gramps_id=gid, 
-                             event_type=models.get_type(models.EventType, the_type),
-                             private=private,
-                             description=description,
-                             last_changed=todate(change))
-        self.add_date(event, date)
-        event.save(save_cache=False)
-
-    def add_event_detail(self, data):
-        (handle, gid, the_type, date, description, place_handle, 
-         citation_list, note_list, media_list, attribute_list,
-         change, tag_list, private) = data
-        try:
-            event = models.Event.objects.get(handle=handle)
-        except:
-            print(("ERROR: Event does not exist: '%s'" % 
-                                  str(handle)), file=sys.stderr)
-            return
-        try:
-            place = models.Place.objects.get(handle=place_handle)
-        except:
-            place = None
-            print(("ERROR: Place does not exist: '%s'" % 
-                                  str(place_handle)), file=sys.stderr)
-        event.place = place
-        event.save(save_cache=False)
-        self.add_note_list(event, note_list)
-        self.add_attribute_list(event, attribute_list)
-        self.add_media_ref_list(event, media_list)
-        self.add_citation_list(event, citation_list)
-        self.add_tag_list(event, tag_list)
-        event.save_cache()
-
-    def get_raw(self, item):
-        """
-        Build and return the raw, serialized data of an object.
-        """
-        if isinstance(item, models.Person):
-            raw = self.get_person(item)
-        elif isinstance(item, models.Family):
-            raw = self.get_family(item)
-        elif isinstance(item, models.Place):
-            raw = self.get_place(item)
-        elif isinstance(item, models.Media):
-            raw = self.get_media(item)
-        elif isinstance(item, models.Source):
-            raw = self.get_source(item)
-        elif isinstance(item, models.Citation):
-            raw = self.get_citation(item)
-        elif isinstance(item, models.Repository):
-            raw = self.get_repository(item)
-        elif isinstance(item, models.Note):
-            raw = self.get_note(item)
-        elif isinstance(item, models.Event):
-            raw = self.get_event(item)
-        else:
-            raise Exception("Don't know how to get raw '%s'" % type(item))
-        return raw
-
-    def check_caches(self, callback=None):
-        """
-        Call this to check the caches for all primary models.
-        """
-        if not isinstance(callback, collections.Callable): 
-            callback = lambda percent: None # dummy
-
-        callback(0)
-        count = 0.0
-        total = (self.Note.all().count() + 
-                 self.Person.all().count() +
-                 self.Event.all().count() + 
-                 self.Family.all().count() +
-                 self.Repository.all().count() +
-                 self.Place.all().count() +
-                 self.Media.all().count() +
-                 self.Source.all().count() +
-                 self.Citation.all().count() +
-                 self.Tag.all().count())
-
-        for item in self.Note.all():
-            raw = self.get_note(item)
-            check_diff(item, raw)
-            count += 1
-        callback(100 * (count/total if total else 0))
-
-        for item in self.Person.all():
-            raw = self.get_person(item)
-            check_diff(item, raw)
-            count += 1
-        callback(100 * (count/total if total else 0))
-
-        for item in self.Family.all():
-            raw = self.get_family(item)
-            check_diff(item, raw)
-            count += 1
-        callback(100 * (count/total if total else 0))
-
-        for item in self.Source.all():
-            raw = self.get_source(item)
-            check_diff(item, raw)
-            count += 1
-        callback(100 * (count/total if total else 0))
-
-        for item in self.Event.all():
-            raw = self.get_event(item)
-            check_diff(item, raw)
-            count += 1
-        callback(100 * (count/total if total else 0))
-
-        for item in self.Repository.all():
-            raw = self.get_repository(item)
-            check_diff(item, raw)
-            count += 1
-        callback(100 * (count/total if total else 0))
-
-        for item in self.Place.all():
-            raw = self.get_place(item)
-            check_diff(item, raw)
-            count += 1
-        callback(100 * (count/total if total else 0))
-
-        for item in self.Media.all():
-            raw = self.get_media(item)
-            check_diff(item, raw)
-            count += 1
-        callback(100 * (count/total if total else 0))
-
-        for item in self.Citation.all():
-            raw = self.get_citation(item)
-            check_diff(item, raw)
-            count += 1
-        callback(100 * (count/total if total else 0))
-
-        for item in self.Tag.all():
-            raw = self.get_tag(item)
-            check_diff(item, raw)
-            count += 1
-        callback(100)
-
-    def check_families(self):
-        """
-        Check family structures.
-        """
-        for family in self.Family.all():
-            if family.mother:
-                if not family in family.mother.families.all():
-                    print("Mother not in family", mother, family)
-            if family.father:
-                if not family in family.father.families.all():
-                    print("Father not in family", mother, family)
-            for child in family.get_children():
-                if family not in child.parent_families.all():
-                    print("Child not in family", child, family)
-        for person in self.Person.all():
-            for family in person.families.all():
-                if person not in [family.mother, family.father]:
-                    print("Spouse not in family", person, family)
-            for family in person.parent_families.all():
-                if person not in family.get_children():
-                    print("Child not in family", person, family)
-
-    def is_public(self, obj, objref):
-        """
-        Returns whether or not an item is "public", and the reason
-        why/why not.
-
-        @param obj - an instance of any Primary object
-        @param objref - one of the PrimaryRef.objects 
-        @return - a tuple containing a boolean (public?) and reason.
-
-        There are three reasons why an item might not be public:
-           1) The item itself is private.
-           2) The item is referenced by a living Person.
-           3) The item is referenced by some other private item.
-        """
-        # If it is private, then no:
-        if obj.private:
-            return (False, "It is marked private.")
-        elif hasattr(obj, "probably_alive") and obj.probably_alive:
-            return (False, "It is marked probaby alive.")
-        elif hasattr(obj, "mother") and obj.mother:
-            public, reason = self.is_public(obj.mother, self.PersonRef)
-            if not public:
-                return public, reason
-        elif hasattr(obj, "father") and obj.father:
-            public, reason = self.is_public(obj.father, self.PersonRef)
-            if not public:
-                return public, reason
-        # FIXME: what about Associations... anything else? Check PrivateProxy
-        if objref:
-            if hasattr(objref.model, "ref_object"):
-                obj_ref_list = objref.filter(ref_object=obj)
-            elif hasattr(objref.model, "citation"):
-                obj_ref_list = objref.filter(citation=obj)
-            else:
-                raise Exception("objref '%s' needs a ref for '%s'" % (objref.model, obj))
-            for reference in obj_ref_list:
-                ref_from_class = reference.object_type.model_class()
-                item = None
-                try:
-                    item = ref_from_class.objects.get(id=reference.object_id)
-                except:
-                    print("Warning: Corrupt reference: %s" % str(reference))
-                    continue
-                # If it is linked to by someone alive? public = False
-                if hasattr(item, "probably_alive") and item.probably_alive:
-                    return (False, "It is referenced by someone who is probaby alive.")
-                # If it is linked to by something private? public = False
-                elif item.private:
-                    return (False, "It is referenced by an item which is marked private.")
-        return (True, "It is visible to the public.")
-
-    def update_public(self, obj, save=True):
-        """
-        >>> dji.update_public(event)
-
-        Given an Event or other instance, update the event's public
-        status, or any event referenced to by the instance.
-
-        For example, if a person is found to be alive, then the
-        referenced events should be marked not public (public = False).
-
-        """
-        from gramps.webapp.utils import probably_alive
-        if obj.__class__.__name__ == "Event":
-            objref = self.EventRef
-        elif obj.__class__.__name__ == "Person":
-            objref = self.PersonRef 
-        elif obj.__class__.__name__ == "Note":
-            objref = self.NoteRef
-        elif obj.__class__.__name__ == "Repository":
-            objref = self.RepositoryRef
-        elif obj.__class__.__name__ == "Citation":
-            objref = self.CitationRef
-        elif obj.__class__.__name__ == "Media":
-            objref = self.MediaRef
-        elif  obj.__class__.__name__ == "Place": # no need for dependency
-            objref = None
-        elif  obj.__class__.__name__ == "Source": # no need for dependency
-            objref = None
-        elif  obj.__class__.__name__ == "Family":
-            objref = self.ChildRef # correct?
-        else:
-            raise Exception("Can't compute public of type '%s'" % str(obj))
-        public, reason = self.is_public(obj, objref) # correct?
-        # Ok, update, if needed:
-        if obj.public != public:
-            obj.public = public
-            if save:
-                print("Updating public:", obj.__class__.__name__, obj.gramps_id)
-                obj.save()
-                #log = self.Log()
-                #log.referenced_by = obj
-                #log.object_id = obj.id
-                #log.object_type = obj_type
-                #log.log_type = "update public status"
-                #log.reason = reason
-                #log.order = 0
-                #log.save()
-
-    def update_publics(self, callback=None):
-        """
-        Call this to update probably_alive for all primary models.
-        """
-        if not isinstance(callback, collections.Callable): 
-            callback = lambda percent: None # dummy
-
-        callback(0)
-        count = 0.0
-        total = (self.Note.all().count() + 
-                 self.Person.all().count() +
-                 self.Event.all().count() + 
-                 self.Family.all().count() +
-                 self.Repository.all().count() +
-                 self.Place.all().count() +
-                 self.Media.all().count() +
-                 self.Source.all().count() +
-                 self.Citation.all().count())
-
-        for item in self.Note.all():
-            self.update_public(item)
-            count += 1
-        callback(100 * (count/total if total else 0))
-
-        for item in self.Person.all():
-            self.update_public(item)
-            count += 1
-        callback(100 * (count/total if total else 0))
-
-        for item in self.Family.all():
-            self.update_public(item)
-            count += 1
-        callback(100 * (count/total if total else 0))
-
-        for item in self.Source.all():
-            self.update_public(item)
-            count += 1
-        callback(100 * (count/total if total else 0))
-
-        for item in self.Event.all():
-            self.update_public(item)
-            count += 1
-        callback(100 * (count/total if total else 0))
-
-        for item in self.Repository.all():
-            self.update_public(item)
-            count += 1
-        callback(100 * (count/total if total else 0))
-
-        for item in self.Place.all():
-            self.update_public(item)
-            count += 1
-        callback(100 * (count/total if total else 0))
-
-        for item in self.Media.all():
-            self.update_public(item)
-            count += 1
-        callback(100 * (count/total if total else 0))
-
-        for item in self.Citation.all():
-            self.update_public(item)
-            count += 1
-        callback(100 * (count/total if total else 0))
-
-    def update_probably_alive(self, callback=None):
-        """
-        Call this to update primary_alive for people.
-        """
-        from gramps.webapp.utils import probably_alive
-        if not isinstance(callback, collections.Callable): 
-            callback = lambda percent: None # dummy
-        callback(0)
-        count = 0.0
-        total = self.Person.all().count()
-        for item in self.Person.all():
-            pa = probably_alive(item.handle)
-            if pa != item.probably_alive:
-                print("Updating probably_alive")
-                item.probably_alive = pa
-                item.save()
-            count += 1
-        callback(100 * (count/total if total else 0))
diff --git a/gramps/plugins/database/djangodb.gpr.py b/gramps/plugins/database/djangodb.gpr.py
deleted file mode 100644
index 2d0d0797b..000000000
--- a/gramps/plugins/database/djangodb.gpr.py
+++ /dev/null
@@ -1,32 +0,0 @@
-#
-# Gramps - a GTK+/GNOME based genealogy program
-#
-# Copyright (C) 2015 Douglas Blank <doug.blank@gmail.com>
-#
-# 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; either version 2 of the License, or
-# (at your option) any later version.
-#
-# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-
-plg = newplugin()
-plg.id    = 'djangodb'
-plg.name  = _("Django Database Backend")
-plg.name_accell  = _("_Django Database Backend")
-plg.description =  _("Django Object Relational Model Database Backend")
-plg.version = '1.0'
-plg.gramps_target_version = "4.2"
-plg.status = STABLE
-plg.fname = 'djangodb.py'
-plg.ptype = DATABASE
-plg.databaseclass = 'DbDjango'
-plg.reset_system = True
diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py
deleted file mode 100644
index a7f64f13e..000000000
--- a/gramps/plugins/database/djangodb.py
+++ /dev/null
@@ -1,2150 +0,0 @@
-# Gramps - a GTK+/GNOME based genealogy program
-#
-# Copyright (C) 2009         Douglas S. Blank <doug.blank@gmail.com>
-#
-# 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; either version 2 of the License, or
-# (at your option) any later version.
-#
-# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-
-""" Implements a Db interface """
-
-#------------------------------------------------------------------------
-#
-# Python Modules
-#
-#------------------------------------------------------------------------
-import sys
-import time
-import re
-import base64
-import pickle
-import os
-import logging
-import shutil
-from django.db import transaction
-
-#------------------------------------------------------------------------
-#
-# Gramps Modules
-#
-#------------------------------------------------------------------------
-import gramps
-from gramps.gen.const import GRAMPS_LOCALE as glocale
-_ = glocale.translation.gettext
-from gramps.gen.lib import (Person, Family, Event, Place, Repository, 
-                            Citation, Source, Note, MediaObject, Tag, 
-                            Researcher, GenderStats)
-from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn
-from gramps.gen.db.undoredo import DbUndo
-from gramps.gen.utils.callback import Callback
-from gramps.gen.updatecallback import UpdateCallback
-from gramps.gen.db import (PERSON_KEY,
-                    FAMILY_KEY,
-                    CITATION_KEY,
-                    SOURCE_KEY,
-                    EVENT_KEY,
-                    MEDIA_KEY,
-                    PLACE_KEY,
-                    REPOSITORY_KEY,
-                    NOTE_KEY)
-from gramps.gen.utils.id import create_id
-from gramps.gen.db.dbconst import *
-
-## add this directory to sys path, so we can find django_support later:
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-
-_LOG = logging.getLogger(DBLOGNAME)
-
-def touch(fname, mode=0o666, dir_fd=None, **kwargs):
-    ## After http://stackoverflow.com/questions/1158076/implement-touch-using-python
-    flags = os.O_CREAT | os.O_APPEND
-    with os.fdopen(os.open(fname, flags=flags, mode=mode, dir_fd=dir_fd)) as f:
-        os.utime(f.fileno() if os.utime in os.supports_fd else fname,
-                 dir_fd=None if os.supports_fd else dir_fd, **kwargs)
-
-class Environment(object):
-    """
-    Implements the Environment API.
-    """
-    def __init__(self, db):
-        self.db = db
-
-    def txn_begin(self):
-        return DjangoTxn("DjangoDb Transaction", self.db)
-
-class Table(object):
-    """
-    Implements Table interface.
-    """
-    def __init__(self, funcs):
-        self.funcs = funcs
-
-    def cursor(self):
-        """
-        Returns a Cursor for this Table.
-        """
-        return self.funcs["cursor_func"]()
-
-    def put(self, key, data, txn=None):
-        ## FIXME: probably needs implementing?
-        #self[key] = data
-        pass
-
-class Map(dict):
-    """
-    Implements the map API for person_map, etc.
-    
-    Takes a Table() as argument.
-    """
-    def __init__(self, tbl, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self.db = tbl
-
-class MetaCursor(object):
-    def __init__(self):
-        pass
-    def __enter__(self):
-        return self
-    def __iter__(self):
-        return self.__next__()
-    def __next__(self):
-        yield None
-    def __exit__(self, *args, **kwargs):
-        pass
-    def iter(self):
-        yield None
-    def first(self):
-        self._iter = self.__iter__()
-        return self.next()
-    def next(self):
-        try:
-            return next(self._iter)
-        except:
-            return None
-    def close(self):
-        pass
-
-class Cursor(object):
-    def __init__(self, model, func):
-        self.model = model
-        self.func = func
-        self._iter = self.__iter__()
-    def __enter__(self):
-        return self
-    def __iter__(self):
-        for item in self.model.all():
-            yield (bytes(item.handle, "utf-8"), self.func(item.handle))
-    def __next__(self):
-        try:
-            return self._iter.__next__()
-        except StopIteration:
-            return None
-    def __exit__(self, *args, **kwargs):
-        pass
-    def iter(self):
-        for item in self.model.all():
-            yield (bytes(item.handle, "utf-8"), self.func(item.handle))
-        yield None
-    def first(self):
-        self._iter = self.__iter__()
-        try:
-            return next(self._iter)
-        except:
-            return
-    def next(self):
-        try:
-            return next(self._iter)
-        except:
-            return None
-    def close(self):
-        pass
-
-class Bookmarks(object):
-    def __init__(self):
-        self.handles = []
-    def get(self):
-        return self.handles
-    def append(self, handle):
-        self.handles.append(handle)
-
-class DjangoTxn(DbTxn):
-    def __init__(self, message, db, table=None):
-        DbTxn.__init__(self, message, db)
-        self.table = table
-
-    def get(self, key, default=None, txn=None, **kwargs):
-        """
-        Returns the data object associated with key
-        """
-        try:
-            return self.table.objects(handle=key)
-        except:
-            if txn and key in txn:
-                return txn[key]
-            else:
-                return None
-
-    def put(self, handle, new_data, txn):
-        """
-        """
-        ## FIXME: probably not correct?
-        txn[handle] = new_data
-
-    def commit(self):
-        pass
-
-class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
-    """
-    A Gramps Database Backend. This replicates the grampsdb functions.
-    """
-    # Set up dictionary for callback signal handler
-    # ---------------------------------------------
-    # 1. Signals for primary objects
-    __signals__ = dict((obj+'-'+op, signal)
-                       for obj in
-                       ['person', 'family', 'event', 'place',
-                        'source', 'citation', 'media', 'note', 'repository', 'tag']
-                       for op, signal in zip(
-                               ['add',   'update', 'delete', 'rebuild'],
-                               [(list,), (list,),  (list,),   None]
-                       )
-                   )
-    
-    # 2. Signals for long operations
-    __signals__.update(('long-op-'+op, signal) for op, signal in zip(
-        ['start',  'heartbeat', 'end'],
-        [(object,), None,       None]
-        ))
-
-    # 3. Special signal for change in home person
-    __signals__['home-person-changed'] = None
-
-    # 4. Signal for change in person group name, parameters are
-    __signals__['person-groupname-rebuild'] = (str, str)
-
-    __callback_map = {}
-
-    def __init__(self, directory=None):
-        DbReadBase.__init__(self)
-        DbWriteBase.__init__(self)
-        Callback.__init__(self)
-        self._tables = {
-            'Person':
-                {
-                "handle_func": self.get_person_from_handle, 
-                "gramps_id_func": self.get_person_from_gramps_id,
-                "class_func": gramps.gen.lib.Person,
-                "cursor_func": self.get_person_cursor,
-                "handles_func": self.get_person_handles,
-                "iter_func": self.iter_people,
-                },
-            'Family':
-                {
-                "handle_func": self.get_family_from_handle, 
-                "gramps_id_func": self.get_family_from_gramps_id,
-                "class_func": gramps.gen.lib.Family,
-                "cursor_func": self.get_family_cursor,
-                "handles_func": self.get_family_handles,
-                "iter_func": self.iter_families,
-                },
-            'Source':
-                {
-                "handle_func": self.get_source_from_handle, 
-                "gramps_id_func": self.get_source_from_gramps_id,
-                "class_func": gramps.gen.lib.Source,
-                "cursor_func": self.get_source_cursor,
-                "handles_func": self.get_source_handles,
-                "iter_func": self.iter_sources,
-                },
-            'Citation':
-                {
-                "handle_func": self.get_citation_from_handle, 
-                "gramps_id_func": self.get_citation_from_gramps_id,
-                "class_func": gramps.gen.lib.Citation,
-                "cursor_func": self.get_citation_cursor,
-                "handles_func": self.get_citation_handles,
-                "iter_func": self.iter_citations,
-                },
-            'Event':
-                {
-                "handle_func": self.get_event_from_handle, 
-                "gramps_id_func": self.get_event_from_gramps_id,
-                "class_func": gramps.gen.lib.Event,
-                "cursor_func": self.get_event_cursor,
-                "handles_func": self.get_event_handles,
-                "iter_func": self.iter_events,
-                },
-            'Media':
-                {
-                "handle_func": self.get_object_from_handle, 
-                "gramps_id_func": self.get_object_from_gramps_id,
-                "class_func": gramps.gen.lib.MediaObject,
-                "cursor_func": self.get_media_cursor,
-                "handles_func": self.get_media_object_handles,
-                "iter_func": self.iter_media_objects,
-                },
-            'Place':
-                {
-                "handle_func": self.get_place_from_handle, 
-                "gramps_id_func": self.get_place_from_gramps_id,
-                "class_func": gramps.gen.lib.Place,
-                "cursor_func": self.get_place_cursor,
-                "handles_func": self.get_place_handles,
-                "iter_func": self.iter_places,
-                },
-            'Repository':
-                {
-                "handle_func": self.get_repository_from_handle, 
-                "gramps_id_func": self.get_repository_from_gramps_id,
-                "class_func": gramps.gen.lib.Repository,
-                "cursor_func": self.get_repository_cursor,
-                "handles_func": self.get_repository_handles,
-                "iter_func": self.iter_repositories,
-                },
-            'Note':
-                {
-                "handle_func": self.get_note_from_handle, 
-                "gramps_id_func": self.get_note_from_gramps_id,
-                "class_func": gramps.gen.lib.Note,
-                "cursor_func": self.get_note_cursor,
-                "handles_func": self.get_note_handles,
-                "iter_func": self.iter_notes,
-                },
-            'Tag':
-                {
-                "handle_func": self.get_tag_from_handle, 
-                "gramps_id_func": None,
-                "class_func": gramps.gen.lib.Tag,
-                "cursor_func": self.get_tag_cursor,
-                "handles_func": self.get_tag_handles,
-                "iter_func": self.iter_tags,
-                },
-            }
-        # skip GEDCOM cross-ref check for now:
-        self.set_feature("skip-check-xref", True)
-        self.readonly = False
-        self.db_is_open = True
-        self.name_formats = []
-        self.bookmarks = Bookmarks()
-        self.undo_callback = None
-        self.redo_callback = None
-        self.undo_history_callback = None
-        self.modified   = 0
-        self.txn = DjangoTxn("DbDjango Transaction", self)
-        self.transaction = None
-        # Import cache for gedcom import, uses transactions, and
-        # two step adding of objects.
-        self.import_cache = {}
-        self.use_import_cache = False
-        self.use_db_cache = True
-        self.undodb = DbUndo(self)
-        self.abort_possible = False
-        self._bm_changes = 0
-        self._directory = directory
-        self.full_name = None
-        self.path = None
-        self.brief_name = None
-        self.genderStats = GenderStats() # can pass in loaded stats as dict
-        self.owner = Researcher()
-        if directory:
-            self.load(directory)
-
-    def load(self, directory, callback=None, mode=None, 
-             force_schema_upgrade=False,
-             force_bsddb_upgrade=False,
-             force_bsddb_downgrade=False,
-             force_python_upgrade=False):
-        _LOG.info("Django loading...")
-        self._directory = directory
-        self.full_name = os.path.abspath(self._directory)
-        self.path = self.full_name
-        self.brief_name = os.path.basename(self._directory)
-        from django.conf import settings
-        default_settings = {"__file__": 
-                            os.path.join(directory, "default_settings.py")}
-        settings_file = os.path.join(directory, "default_settings.py")
-        with open(settings_file) as f:
-            code = compile(f.read(), settings_file, 'exec')
-            exec(code, globals(), default_settings)
-
-        class Module(object):
-            def __init__(self, dictionary):
-                self.dictionary = dictionary
-            def __getattr__(self, item):
-                return self.dictionary[item]
-
-        _LOG.info("Django loading defaults from: " + directory)
-        try:
-            settings.configure(Module(default_settings))
-        except RuntimeError:
-            _LOG.info("Django already configured error! Shouldn't happen!")
-            # already configured; ignore
-            pass
-
-        import django
-        django.setup()
-
-        from django_support.libdjango import DjangoInterface
-        self.dji = DjangoInterface()
-        self.family_bookmarks = Bookmarks()
-        self.event_bookmarks = Bookmarks()
-        self.place_bookmarks = Bookmarks()
-        self.citation_bookmarks = Bookmarks()
-        self.source_bookmarks = Bookmarks()
-        self.repo_bookmarks = Bookmarks()
-        self.media_bookmarks = Bookmarks()
-        self.note_bookmarks = Bookmarks()
-        self.set_person_id_prefix('I%04d')
-        self.set_object_id_prefix('O%04d')
-        self.set_family_id_prefix('F%04d')
-        self.set_citation_id_prefix('C%04d')
-        self.set_source_id_prefix('S%04d')
-        self.set_place_id_prefix('P%04d')
-        self.set_event_id_prefix('E%04d')
-        self.set_repository_id_prefix('R%04d')
-        self.set_note_id_prefix('N%04d')
-        # ----------------------------------
-        self.id_trans  = DjangoTxn("ID Transaction", self, self.dji.Person)
-        self.fid_trans = DjangoTxn("FID Transaction", self, self.dji.Family)
-        self.pid_trans = DjangoTxn("PID Transaction", self, self.dji.Place)
-        self.cid_trans = DjangoTxn("CID Transaction", self, self.dji.Citation)
-        self.sid_trans = DjangoTxn("SID Transaction", self, self.dji.Source)
-        self.oid_trans = DjangoTxn("OID Transaction", self, self.dji.Media)
-        self.rid_trans = DjangoTxn("RID Transaction", self, self.dji.Repository)
-        self.nid_trans = DjangoTxn("NID Transaction", self, self.dji.Note)
-        self.eid_trans = DjangoTxn("EID Transaction", self, self.dji.Event)
-        self.cmap_index = 0
-        self.smap_index = 0
-        self.emap_index = 0
-        self.pmap_index = 0
-        self.fmap_index = 0
-        self.lmap_index = 0
-        self.omap_index = 0
-        self.rmap_index = 0
-        self.nmap_index = 0
-        self.env = Environment(self)
-        self.person_map = Map(Table(self._tables["Person"]))
-        self.family_map = Map(Table(self._tables["Family"]))
-        self.place_map  = Map(Table(self._tables["Place"]))
-        self.citation_map = Map(Table(self._tables["Citation"]))
-        self.source_map = Map(Table(self._tables["Source"]))
-        self.repository_map  = Map(Table(self._tables["Repository"]))
-        self.note_map = Map(Table(self._tables["Note"]))
-        self.media_map  = Map(Table(self._tables["Media"]))
-        self.event_map  = Map(Table(self._tables["Event"]))
-        self.tag_map  = Map(Table(self._tables["Tag"]))
-        self.metadata   = Map(Table({"cursor_func": lambda: MetaCursor()}))
-        self.name_group = {}
-        self.event_names = set()
-        self.individual_attributes = set()
-        self.family_attributes = set()
-        self.source_attributes = set()
-        self.child_ref_types = set()
-        self.family_rel_types = set()
-        self.event_role_names = set()
-        self.name_types = set()
-        self.origin_types = set()
-        self.repository_types = set()
-        self.note_types = set()
-        self.source_media_types = set()
-        self.url_types = set()
-        self.media_attributes = set()
-        self.place_types = set()
-        _LOG.info("Django loading... done!")
-
-    def prepare_import(self):
-        """
-        DbDjango does not commit data on gedcom import, but saves them
-        for later commit.
-        """
-        self.use_import_cache = True
-        self.import_cache = {}
-
-    @transaction.atomic
-    def commit_import(self):
-        """
-        Commits the items that were queued up during the last gedcom
-        import for two step adding.
-        """
-        # First we add the primary objects:
-        for key in list(self.import_cache.keys()):
-            obj = self.import_cache[key]
-            if isinstance(obj, Person):
-                self.dji.add_person(obj.serialize())
-            elif isinstance(obj, Family):
-                self.dji.add_family(obj.serialize())
-            elif isinstance(obj, Event):
-                self.dji.add_event(obj.serialize())
-            elif isinstance(obj, Place):
-                self.dji.add_place(obj.serialize())
-            elif isinstance(obj, Repository):
-                self.dji.add_repository(obj.serialize())
-            elif isinstance(obj, Citation):
-                self.dji.add_citation(obj.serialize())
-            elif isinstance(obj, Source):
-                self.dji.add_source(obj.serialize())
-            elif isinstance(obj, Note):
-                self.dji.add_note(obj.serialize())
-            elif isinstance(obj, MediaObject):
-                self.dji.add_media(obj.serialize())
-            elif isinstance(obj, Tag):
-                self.dji.add_tag(obj.serialize())
-        # Next we add the links:
-        for key in list(self.import_cache.keys()):
-            obj = self.import_cache[key]
-            if isinstance(obj, Person):
-                self.dji.add_person_detail(obj.serialize())
-            elif isinstance(obj, Family):
-                self.dji.add_family_detail(obj.serialize())
-            elif isinstance(obj, Event):
-                self.dji.add_event_detail(obj.serialize())
-            elif isinstance(obj, Place):
-                self.dji.add_place_detail(obj.serialize())
-            elif isinstance(obj, Repository):
-                self.dji.add_repository_detail(obj.serialize())
-            elif isinstance(obj, Citation):
-                self.dji.add_citation_detail(obj.serialize())
-            elif isinstance(obj, Source):
-                self.dji.add_source_detail(obj.serialize())
-            elif isinstance(obj, Note):
-                self.dji.add_note_detail(obj.serialize())
-            elif isinstance(obj, MediaObject):
-                self.dji.add_media_detail(obj.serialize())
-            elif isinstance(obj, Tag):
-                self.dji.add_tag_detail(obj.serialize())
-        self.use_import_cache = False
-        self.import_cache = {}
-        self.request_rebuild()
-
-    def transaction_commit(self, txn):
-        pass
-
-    def request_rebuild(self):
-        # caches are ok, but let's compute public's
-        self.dji.update_publics()
-        self.emit('person-rebuild')
-        self.emit('family-rebuild')
-        self.emit('place-rebuild')
-        self.emit('source-rebuild')
-        self.emit('citation-rebuild')
-        self.emit('media-rebuild')
-        self.emit('event-rebuild')
-        self.emit('repository-rebuild')
-        self.emit('note-rebuild')
-        self.emit('tag-rebuild')
-
-    def get_undodb(self):
-        return None
-
-    def transaction_abort(self, txn):
-        pass
-
-    @staticmethod
-    def _validated_id_prefix(val, default):
-        if isinstance(val, str) and val:
-            try:
-                str_ = val % 1
-            except TypeError:           # missing conversion specifier
-                prefix_var = val + "%d"
-            except ValueError:          # incomplete format
-                prefix_var = default+"%04d"
-            else:
-                prefix_var = val        # OK as given
-        else:
-            prefix_var = default+"%04d" # not a string or empty string
-        return prefix_var
-
-    @staticmethod
-    def __id2user_format(id_pattern):
-        """
-        Return a method that accepts a Gramps ID and adjusts it to the users
-        format.
-        """
-        pattern_match = re.match(r"(.*)%[0 ](\d+)[diu]$", id_pattern)
-        if pattern_match:
-            str_prefix = pattern_match.group(1)
-            nr_width = pattern_match.group(2)
-            def closure_func(gramps_id):
-                if gramps_id and gramps_id.startswith(str_prefix):
-                    id_number = gramps_id[len(str_prefix):]
-                    if id_number.isdigit():
-                        id_value = int(id_number, 10)
-                        #if len(str(id_value)) > nr_width:
-                        #    # The ID to be imported is too large to fit in the
-                        #    # users format. For now just create a new ID,
-                        #    # because that is also what happens with IDs that
-                        #    # are identical to IDs already in the database. If
-                        #    # the problem of colliding import and already
-                        #    # present IDs is solved the code here also needs
-                        #    # some solution.
-                        #    gramps_id = id_pattern % 1
-                        #else:
-                        gramps_id = id_pattern % id_value
-                return gramps_id
-        else:
-            def closure_func(gramps_id):
-                return gramps_id
-        return closure_func
-
-    def set_person_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Person ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as I%d or I%04d.
-        """
-        self.person_prefix = self._validated_id_prefix(val, "I")
-        self.id2user_format = self.__id2user_format(self.person_prefix)
-
-    def set_citation_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Citation ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as C%d or C%04d.
-        """
-        self.citation_prefix = self._validated_id_prefix(val, "C")
-        self.cid2user_format = self.__id2user_format(self.citation_prefix)
-            
-    def set_source_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Source ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as S%d or S%04d.
-        """
-        self.source_prefix = self._validated_id_prefix(val, "S")
-        self.sid2user_format = self.__id2user_format(self.source_prefix)
-            
-    def set_object_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS MediaObject ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as O%d or O%04d.
-        """
-        self.mediaobject_prefix = self._validated_id_prefix(val, "O")
-        self.oid2user_format = self.__id2user_format(self.mediaobject_prefix)
-
-    def set_place_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Place ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as P%d or P%04d.
-        """
-        self.place_prefix = self._validated_id_prefix(val, "P")
-        self.pid2user_format = self.__id2user_format(self.place_prefix)
-
-    def set_family_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Family ID values. The string is
-        expected to be in the form of a simple text string, or in a format
-        that contains a C/Python style format string using %d, such as F%d
-        or F%04d.
-        """
-        self.family_prefix = self._validated_id_prefix(val, "F")
-        self.fid2user_format = self.__id2user_format(self.family_prefix)
-
-    def set_event_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Event ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as E%d or E%04d.
-        """
-        self.event_prefix = self._validated_id_prefix(val, "E")
-        self.eid2user_format = self.__id2user_format(self.event_prefix)
-
-    def set_repository_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Repository ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as R%d or R%04d.
-        """
-        self.repository_prefix = self._validated_id_prefix(val, "R")
-        self.rid2user_format = self.__id2user_format(self.repository_prefix)
-
-    def set_note_id_prefix(self, val):
-        """
-        Set the naming template for GRAMPS Note ID values. 
-        
-        The string is expected to be in the form of a simple text string, or 
-        in a format that contains a C/Python style format string using %d, 
-        such as N%d or N%04d.
-        """
-        self.note_prefix = self._validated_id_prefix(val, "N")
-        self.nid2user_format = self.__id2user_format(self.note_prefix)
-
-    def __find_next_gramps_id(self, prefix, map_index, trans):
-        """
-        Helper function for find_next_<object>_gramps_id methods
-        """
-        index = prefix % map_index
-        while trans.get(str(index), txn=self.txn) is not None:
-            map_index += 1
-            index = prefix % map_index
-        map_index += 1
-        return (map_index, index)
-        
-    def find_next_person_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Person object based off the 
-        person ID prefix.
-        """
-        self.pmap_index, gid = self.__find_next_gramps_id(self.person_prefix,
-                                          self.pmap_index, self.id_trans)
-        return gid
-
-    def find_next_place_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Place object based off the 
-        place ID prefix.
-        """
-        self.lmap_index, gid = self.__find_next_gramps_id(self.place_prefix,
-                                          self.lmap_index, self.pid_trans)
-        return gid
-
-    def find_next_event_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Event object based off the 
-        event ID prefix.
-        """
-        self.emap_index, gid = self.__find_next_gramps_id(self.event_prefix,
-                                          self.emap_index, self.eid_trans)
-        return gid
-
-    def find_next_object_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a MediaObject object based
-        off the media object ID prefix.
-        """
-        self.omap_index, gid = self.__find_next_gramps_id(self.mediaobject_prefix,
-                                          self.omap_index, self.oid_trans)
-        return gid
-
-    def find_next_citation_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Citation object based off the 
-        citation ID prefix.
-        """
-        self.cmap_index, gid = self.__find_next_gramps_id(self.citation_prefix,
-                                          self.cmap_index, self.cid_trans)
-        return gid
-
-    def find_next_source_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Source object based off the 
-        source ID prefix.
-        """
-        self.smap_index, gid = self.__find_next_gramps_id(self.source_prefix,
-                                          self.smap_index, self.sid_trans)
-        return gid
-
-    def find_next_family_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Family object based off the 
-        family ID prefix.
-        """
-        self.fmap_index, gid = self.__find_next_gramps_id(self.family_prefix,
-                                          self.fmap_index, self.fid_trans)
-        return gid
-
-    def find_next_repository_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Respository object based 
-        off the repository ID prefix.
-        """
-        self.rmap_index, gid = self.__find_next_gramps_id(self.repository_prefix,
-                                          self.rmap_index, self.rid_trans)
-        return gid
-
-    def find_next_note_gramps_id(self):
-        """
-        Return the next available GRAMPS' ID for a Note object based off the 
-        note ID prefix.
-        """
-        self.nmap_index, gid = self.__find_next_gramps_id(self.note_prefix,
-                                          self.nmap_index, self.nid_trans)
-        return gid
-
-    def get_mediapath(self):
-        return None
-
-    def get_name_group_keys(self):
-        return []
-
-    def get_name_group_mapping(self, key):
-        return None
-
-    def get_researcher(self):
-        return self.owner
-
-    def get_tag_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Tag.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Tag.all()]
-
-    def get_person_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Person.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Person.all()]
-
-    def get_family_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Family.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Family.all()]
-
-    def get_event_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Event.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Event.all()]
-
-    def get_citation_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Citation.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Citation.all()]
-
-    def get_source_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Source.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Source.all()]
-
-    def get_place_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Place.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Place.all()]
-
-    def get_repository_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Repository.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Repository.all()]
-
-    def get_media_object_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Media.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Media.all()]
-
-    def get_note_handles(self, sort_handles=False):
-        if sort_handles:
-            return [item.handle for item in self.dji.Note.all().order_by("handle")]
-        else:
-            return [item.handle for item in self.dji.Note.all()]
-
-    def get_media_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            media = self.dji.Media.get(handle=handle)
-        except:
-            return None
-        return self.make_media(media)
-
-    def get_event_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            event = self.dji.Event.get(handle=handle)
-        except:
-            return None
-        return self.make_event(event)
-
-    def get_family_from_handle(self, handle): 
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            family = self.dji.Family.get(handle=handle)
-        except:
-            return None
-        return self.make_family(family)
-
-    def get_repository_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            repository = self.dji.Repository.get(handle=handle)
-        except:
-            return None
-        return self.make_repository(repository)
-
-    def get_person_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            person = self.dji.Person.get(handle=handle)
-        except:
-            return None
-        return self.make_person(person)
-
-    def get_tag_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            tag = self.dji.Tag.get(handle=handle)
-        except:
-            return None
-        return self.make_tag(tag)
-
-    def make_repository(self, repository):
-        if self.use_db_cache and repository.cache:
-            data = repository.from_cache()
-        else:
-            data = self.dji.get_repository(repository)
-        return Repository.create(data)
-
-    def make_citation(self, citation):
-        if self.use_db_cache and citation.cache:
-            data = citation.from_cache()
-        else:
-            data = self.dji.get_citation(citation)
-        return Citation.create(data)
-
-    def make_source(self, source):
-        if self.use_db_cache and source.cache:
-            data = source.from_cache()
-        else:
-            data = self.dji.get_source(source)
-        return Source.create(data)
-
-    def make_family(self, family):
-        if self.use_db_cache and family.cache:
-            data = family.from_cache()
-        else:
-            data = self.dji.get_family(family)
-        return Family.create(data)
-
-    def make_person(self, person):
-        if self.use_db_cache and person.cache:
-            data = person.from_cache()
-        else:
-            data = self.dji.get_person(person)
-        return Person.create(data)
-
-    def make_event(self, event):
-        if self.use_db_cache and event.cache:
-            data = event.from_cache()
-        else:
-            data = self.dji.get_event(event)
-        return Event.create(data)
-
-    def make_note(self, note):
-        if self.use_db_cache and note.cache:
-            data = note.from_cache()
-        else:
-            data = self.dji.get_note(note)
-        return Note.create(data)
-
-    def make_tag(self, tag):
-        data = self.dji.get_tag(tag)
-        return Tag.create(data)
-
-    def make_place(self, place):
-        if self.use_db_cache and place.cache:
-            data = place.from_cache()
-        else:
-            data = self.dji.get_place(place)
-        return Place.create(data)
-
-    def make_media(self, media):
-        if self.use_db_cache and media.cache:
-            data = media.from_cache()
-        else:
-            data = self.dji.get_media(media)
-        return MediaObject.create(data)
-
-    def get_place_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            place = self.dji.Place.get(handle=handle)
-        except:
-            return None
-        return self.make_place(place)
-
-    def get_citation_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            citation = self.dji.Citation.get(handle=handle)
-        except:
-            return None
-        return self.make_citation(citation)
-
-    def get_source_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            source = self.dji.Source.get(handle=handle)
-        except:
-            return None
-        return self.make_source(source)
-
-    def get_note_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            note = self.dji.Note.get(handle=handle)
-        except:
-            return None
-        return self.make_note(note)
-
-    def get_object_from_handle(self, handle):
-        if handle in self.import_cache:
-            return self.import_cache[handle]
-        try:
-            media = self.dji.Media.get(handle=handle)
-        except:
-            return None
-        return self.make_media(media)
-
-    def get_default_person(self):
-        people = self.dji.Person.all()
-        if people.count() > 0:
-            return self.make_person(people[0])
-        return None
-
-    def iter_people(self):
-        return (self.get_person_from_handle(person.handle) 
-                for person in self.dji.Person.all())
-
-    def iter_person_handles(self):
-        return (person.handle for person in self.dji.Person.all())
-
-    def iter_families(self):
-        return (self.get_family_from_handle(family.handle) 
-                for family in self.dji.Family.all())
-
-    def iter_family_handles(self):
-        return (family.handle for family in self.dji.Family.all())
-
-    def iter_notes(self):
-        return (self.get_note_from_handle(note.handle) 
-                for note in self.dji.Note.all())
-
-    def iter_note_handles(self):
-        return (note.handle for note in self.dji.Note.all())
-
-    def iter_events(self):
-        return (self.get_event_from_handle(event.handle) 
-                for event in self.dji.Event.all())
-
-    def iter_event_handles(self):
-        return (event.handle for event in self.dji.Event.all())
-
-    def iter_places(self):
-        return (self.get_place_from_handle(place.handle) 
-                for place in self.dji.Place.all())
-
-    def iter_place_handles(self):
-        return (place.handle for place in self.dji.Place.all())
-
-    def iter_repositories(self):
-        return (self.get_repository_from_handle(repository.handle) 
-                for repository in self.dji.Repository.all())
-
-    def iter_repository_handles(self):
-        return (repository.handle for repository in self.dji.Repository.all())
-
-    def iter_sources(self):
-        return (self.get_source_from_handle(source.handle) 
-                for source in self.dji.Source.all())
-
-    def iter_source_handles(self):
-        return (source.handle for source in self.dji.Source.all())
-
-    def iter_citations(self):
-        return (self.get_citation_from_handle(citation.handle) 
-                for citation in self.dji.Citation.all())
-
-    def iter_citation_handles(self):
-        return (citation.handle for citation in self.dji.Citation.all())
-
-    def iter_tags(self):
-        return (self.get_tag_from_handle(tag.handle) 
-                for tag in self.dji.Tag.all())
-
-    def iter_tag_handles(self):
-        return (tag.handle for tag in self.dji.Tag.all())
-
-    def iter_media_objects(self):
-        return (self.get_media_from_handle(media.handle) 
-                for media in self.dji.Media.all())
-
-    def get_tag_from_name(self, name):
-        try:
-            tag = self.dji.Tag.filter(name=name)
-            return self.make_tag(tag[0])
-        except:
-            return None
-
-    def get_person_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        match_list = self.dji.Person.filter(gramps_id=gramps_id)
-        if match_list.count() > 0:
-            return self.make_person(match_list[0])
-        else:
-            return None
-
-    def get_family_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        try:
-            family = self.dji.Family.get(gramps_id=gramps_id)
-        except:
-            return None
-        return self.make_family(family)
-
-    def get_source_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        match_list = self.dji.Source.filter(gramps_id=gramps_id)
-        if match_list.count() > 0:
-            return self.make_source(match_list[0])
-        else:
-            return None
-
-    def get_citation_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        match_list = self.dji.Citation.filter(gramps_id=gramps_id)
-        if match_list.count() > 0:
-            return self.make_citation(match_list[0])
-        else:
-            return None
-
-    def get_event_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        match_list = self.dji.Event.filter(gramps_id=gramps_id)
-        if match_list.count() > 0:
-            return self.make_event(match_list[0])
-        else:
-            return None
-
-    def get_object_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        match_list = self.dji.Media.filter(gramps_id=gramps_id)
-        if match_list.count() > 0:
-            return self.make_media(match_list[0])
-        else:
-            return None
-
-    def get_place_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        match_list = self.dji.Place.filter(gramps_id=gramps_id)
-        if match_list.count() > 0:
-            return self.make_place(match_list[0])
-        else:
-            return None
-
-    def get_repository_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        match_list = self.dji.Repsoitory.filter(gramps_id=gramps_id)
-        if match_list.count() > 0:
-            return self.make_repository(match_list[0])
-        else:
-            return None
-
-    def get_note_from_gramps_id(self, gramps_id):
-        if self.import_cache:
-            for handle in self.import_cache:
-                if self.import_cache[handle].gramps_id == gramps_id:
-                    return self.import_cache[handle]
-        match_list = self.dji.Note.filter(gramps_id=gramps_id)
-        if match_list.count() > 0:
-            return self.make_note(match_list[0])
-        else:
-            return None
-
-    def get_number_of_people(self):
-        return self.dji.Person.count()
-
-    def get_number_of_events(self):
-        return self.dji.Event.count()
-
-    def get_number_of_places(self):
-        return self.dji.Place.count()
-
-    def get_number_of_tags(self):
-        return self.dji.Tag.count()
-
-    def get_number_of_families(self):
-        return self.dji.Family.count()
-
-    def get_number_of_notes(self):
-        return self.dji.Note.count()
-
-    def get_number_of_citations(self):
-        return self.dji.Citation.count()
-
-    def get_number_of_sources(self):
-        return self.dji.Source.count()
-
-    def get_number_of_media_objects(self):
-        return self.dji.Media.count()
-
-    def get_number_of_repositories(self):
-        return self.dji.Repository.count()
-
-    def get_place_cursor(self):
-        return Cursor(self.dji.Place, self.get_raw_place_data)
-
-    def get_person_cursor(self):
-        return Cursor(self.dji.Person, self.get_raw_person_data)
-
-    def get_family_cursor(self):
-        return Cursor(self.dji.Family, self.get_raw_family_data)
-
-    def get_event_cursor(self):
-        return Cursor(self.dji.Event, self.get_raw_event_data)
-
-    def get_citation_cursor(self):
-        return Cursor(self.dji.Citation, self.get_raw_citation_data)
-
-    def get_source_cursor(self):
-        return Cursor(self.dji.Source, self.get_raw_source_data)
-
-    def get_note_cursor(self):
-        return Cursor(self.dji.Note, self.get_raw_note_data)
-
-    def get_tag_cursor(self):
-        return Cursor(self.dji.Tag, self.get_raw_tag_data)
-
-    def get_repository_cursor(self):
-        return Cursor(self.dji.Repository, self.get_raw_repository_data)
-
-    def get_media_cursor(self):
-        return Cursor(self.dji.Media, self.get_raw_object_data)
-
-    def has_gramps_id(self, obj_key, gramps_id):
-        key2table = {
-            PERSON_KEY:     self.dji.Person, 
-            FAMILY_KEY:     self.dji.Family, 
-            SOURCE_KEY:     self.dji.Source, 
-            CITATION_KEY:   self.dji.Citation, 
-            EVENT_KEY:      self.dji.Event, 
-            MEDIA_KEY:      self.dji.Media, 
-            PLACE_KEY:      self.dji.Place, 
-            REPOSITORY_KEY: self.dji.Repository, 
-            NOTE_KEY:       self.dji.Note, 
-            }
-        table = key2table[obj_key]
-        return table.filter(gramps_id=gramps_id).count() > 0
-
-    def has_person_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Person.filter(handle=handle).count() == 1
-
-    def has_family_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Family.filter(handle=handle).count() == 1
-
-    def has_citation_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Citation.filter(handle=handle).count() == 1
-
-    def has_source_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Source.filter(handle=handle).count() == 1
-
-    def has_repository_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Repository.filter(handle=handle).count() == 1
-
-    def has_note_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Note.filter(handle=handle).count() == 1
-
-    def has_place_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Place.filter(handle=handle).count() == 1
-
-    def has_event_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Event.filter(handle=handle).count() == 1
-
-    def has_tag_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Tag.filter(handle=handle).count() == 1
-
-    def has_object_handle(self, handle):
-        if handle in self.import_cache:
-            return True
-        return self.dji.Media.filter(handle=handle).count() == 1
-
-    def has_name_group_key(self, key):
-        # FIXME:
-        return False
-
-    def set_name_group_mapping(self, key, value):
-        # FIXME:
-        pass
-
-    def set_default_person_handle(self, handle):
-        pass
-
-    def set_mediapath(self, mediapath):
-        pass
-
-    def get_raw_person_data(self, handle):
-        try:
-            return self.dji.get_person(self.dji.Person.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_family_data(self, handle):
-        try:
-            return self.dji.get_family(self.dji.Family.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_citation_data(self, handle):
-        try:
-            return self.dji.get_citation(self.dji.Citation.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_source_data(self, handle):
-        try:
-            return self.dji.get_source(self.dji.Source.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_repository_data(self, handle):
-        try:
-            return self.dji.get_repository(self.dji.Repository.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_note_data(self, handle):
-        try:
-            return self.dji.get_note(self.dji.Note.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_place_data(self, handle):
-        try:
-            return self.dji.get_place(self.dji.Place.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_object_data(self, handle):
-        try:
-            return self.dji.get_media(self.dji.Media.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_tag_data(self, handle):
-        try:
-            return self.dji.get_tag(self.dji.Tag.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def get_raw_event_data(self, handle):
-        try:
-            return self.dji.get_event(self.dji.Event.get(handle=handle))
-        except:
-            if handle in self.import_cache:
-                return self.import_cache[handle].serialize()
-            else:
-                return None
-
-    def add_person(self, person, trans, set_gid=True):
-        if not person.handle:
-            person.handle = create_id()
-        if not person.gramps_id:
-            person.gramps_id = self.find_next_person_gramps_id()
-        self.commit_person(person, trans)
-        return person.handle
-
-    def add_family(self, family, trans, set_gid=True):
-        if not family.handle:
-            family.handle = create_id()
-        if not family.gramps_id:
-            family.gramps_id = self.find_next_family_gramps_id()
-        self.commit_family(family, trans)
-        return family.handle
-
-    def add_citation(self, citation, trans, set_gid=True):
-        if not citation.handle:
-            citation.handle = create_id()
-        if not citation.gramps_id:
-            citation.gramps_id = self.find_next_citation_gramps_id()
-        self.commit_citation(citation, trans)
-        return citation.handle
-
-    def add_source(self, source, trans, set_gid=True):
-        if not source.handle:
-            source.handle = create_id()
-        if not source.gramps_id:
-            source.gramps_id = self.find_next_source_gramps_id()
-        self.commit_source(source, trans)
-        return source.handle
-
-    def add_repository(self, repository, trans, set_gid=True):
-        if not repository.handle:
-            repository.handle = create_id()
-        if not repository.gramps_id:
-            repository.gramps_id = self.find_next_repository_gramps_id()
-        self.commit_repository(repository, trans)
-        return repository.handle
-
-    def add_note(self, note, trans, set_gid=True):
-        if not note.handle:
-            note.handle = create_id()
-        if not note.gramps_id:
-            note.gramps_id = self.find_next_note_gramps_id()
-        self.commit_note(note, trans)
-        return note.handle
-
-    def add_place(self, place, trans, set_gid=True):
-        if not place.handle:
-            place.handle = create_id()
-        if not place.gramps_id:
-            place.gramps_id = self.find_next_place_gramps_id()
-        self.commit_place(place, trans)
-        return place.handle
-
-    def add_event(self, event, trans, set_gid=True):
-        if not event.handle:
-            event.handle = create_id()
-        if not event.gramps_id:
-            event.gramps_id = self.find_next_event_gramps_id()
-        self.commit_event(event, trans)
-        return event.handle
-
-    def add_tag(self, tag, trans):
-        if not tag.handle:
-            tag.handle = create_id()
-        self.commit_tag(tag, trans)
-        return tag.handle
-
-    def add_object(self, obj, transaction, set_gid=True):
-        """
-        Add a MediaObject to the database, assigning internal IDs if they have
-        not already been defined.
-        
-        If not set_gid, then gramps_id is not set.
-        """
-        if not obj.handle:
-            obj.handle = create_id()
-        if not obj.gramps_id:
-            obj.gramps_id = self.find_next_object_gramps_id()
-        self.commit_media_object(obj, transaction)
-        return obj.handle
-
-    def commit_person(self, person, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[person.handle] = person
-        else:
-            raw = person.serialize()
-            items = self.dji.Person.filter(handle=person.handle)
-            count = items.count()
-            if count > 0:
-                # Hack, for the moment: delete and re-add
-                items[0].delete()
-            self.dji.add_person(person.serialize())
-            self.dji.add_person_detail(person.serialize())
-            if not trans.batch:
-                if count > 0:
-                    self.emit("person-update", ([person.handle],))
-                else:
-                    self.emit("person-add", ([person.handle],))
-
-    def commit_family(self, family, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[family.handle] = family
-        else:
-            raw = family.serialize()
-            items = self.dji.Family.filter(handle=family.handle)
-            count = items.count()
-            if count > 0:
-                items[0].delete()
-            self.dji.add_family(family.serialize())
-            self.dji.add_family_detail(family.serialize())
-            if not trans.batch:
-                if count > 0:
-                    self.emit("family-update", ([family.handle],))
-                else:
-                    self.emit("family-add", ([family.handle],))
-
-    def commit_citation(self, citation, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[citation.handle] = citation
-        else:
-            raw = citation.serialize()
-            items = self.dji.Citation.filter(handle=citation.handle)
-            count = items.count()
-            if count > 0:
-                items[0].delete()
-            self.dji.add_citation(citation.serialize())
-            self.dji.add_citation_detail(citation.serialize())
-            if not trans.batch:
-                if count > 0:
-                    self.emit("citation-update", ([citation.handle],))
-                else:
-                    self.emit("citation-add", ([citation.handle],))
-
-    def commit_source(self, source, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[source.handle] = source
-        else:
-            raw = source.serialize()
-            items = self.dji.Source.filter(handle=source.handle)
-            count = items.count()
-            if count > 0:
-                items[0].delete()
-            self.dji.add_source(source.serialize())
-            self.dji.add_source_detail(source.serialize())
-            if not trans.batch:
-                if count > 0:
-                    self.emit("source-update", ([source.handle],))
-                else:
-                    self.emit("source-add", ([source.handle],))
-
-    def commit_repository(self, repository, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[repository.handle] = repository
-        else:
-            raw = repository.serialize()
-            items = self.dji.Repository.filter(handle=repository.handle)
-            count = items.count()
-            if count > 0:
-                items[0].delete()
-            self.dji.add_repository(repository.serialize())
-            self.dji.add_repository_detail(repository.serialize())
-            if not trans.batch:
-                if count > 0:
-                    self.emit("repository-update", ([repository.handle],))
-                else:
-                    self.emit("repository-add", ([repository.handle],))
-
-    def commit_note(self, note, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[note.handle] = note
-        else:
-            raw = note.serialize()
-            items = self.dji.Note.filter(handle=note.handle)
-            count = items.count()
-            if count > 0:
-                items[0].delete()
-            self.dji.add_note(note.serialize())
-            self.dji.add_note_detail(note.serialize())
-            if not trans.batch:
-                if count > 0:
-                    self.emit("note-update", ([note.handle],))
-                else:
-                    self.emit("note-add", ([note.handle],))
-
-    def commit_place(self, place, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[place.handle] = place
-        else:
-            raw = place.serialize()
-            items = self.dji.Place.filter(handle=place.handle)
-            count = items.count()
-            if count > 0:
-                items[0].delete()
-            self.dji.add_place(place.serialize())
-            self.dji.add_place_detail(place.serialize())
-            if not trans.batch:
-                if count > 0:
-                    self.emit("place-update", ([place.handle],))
-                else:
-                    self.emit("place-add", ([place.handle],))
-
-    def commit_event(self, event, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[event.handle] = event
-        else:
-            raw = event.serialize()
-            items = self.dji.Event.filter(handle=event.handle)
-            count = items.count()
-            if count > 0:
-                items[0].delete()
-            self.dji.add_event(event.serialize())
-            self.dji.add_event_detail(event.serialize())
-            if not trans.batch:
-                if count > 0:
-                    self.emit("event-update", ([event.handle],))
-                else:
-                    self.emit("event-add", ([event.handle],))
-
-    def commit_tag(self, tag, trans, change_time=None):
-        if self.use_import_cache:
-            self.import_cache[tag.handle] = tag
-        else:
-            raw = tag.serialize()
-            items = self.dji.Tag.filter(handle=tag.handle)
-            count = items.count()
-            if count > 0:
-                items[0].delete()
-            self.dji.add_tag(tag.serialize())
-            self.dji.add_tag_detail(tag.serialize())
-            if not trans.batch:
-                if count > 0:
-                    self.emit("tag-update", ([tag.handle],))
-                else:
-                    self.emit("tag-add", ([tag.handle],))
-
-    def commit_media_object(self, media, trans, change_time=None):
-        """
-        Commit the specified MediaObject to the database, storing the changes
-        as part of the transaction.
-        """
-        if self.use_import_cache:
-            self.import_cache[media.handle] = media
-        else:
-            raw = media.serialize()
-            items = self.dji.Media.filter(handle=media.handle)
-            count = items.count()
-            if count > 0:
-                items[0].delete()
-            self.dji.add_media(media.serialize())
-            self.dji.add_media_detail(media.serialize())
-            if not trans.batch:
-                if count > 0:
-                    self.emit("media-update", ([media.handle],))
-                else:
-                    self.emit("media-add", ([media.handle],))
-
-    def get_gramps_ids(self, obj_key):
-        key2table = {
-            PERSON_KEY:     self.id_trans, 
-            FAMILY_KEY:     self.fid_trans, 
-            CITATION_KEY:   self.cid_trans, 
-            SOURCE_KEY:     self.sid_trans, 
-            EVENT_KEY:      self.eid_trans, 
-            MEDIA_KEY:      self.oid_trans, 
-            PLACE_KEY:      self.pid_trans, 
-            REPOSITORY_KEY: self.rid_trans, 
-            NOTE_KEY:       self.nid_trans, 
-            }
-
-        table = key2table[obj_key]
-        return list(table.keys())
-
-    def transaction_begin(self, transaction):
-        return 
-
-    def set_researcher(self, owner):
-        self.owner.set_from(owner)
-
-    def copy_from_db(self, db):
-        """
-        A (possibily) implementation-specific method to get data from
-        db into this database.
-        """
-        # First we add the primary objects:
-        for key in db._tables.keys():
-            cursor = db._tables[key]["cursor_func"]
-            for (handle, data) in cursor():
-                if key == "Person":
-                    self.dji.add_person(data)
-                elif key == "Family":
-                    self.dji.add_family(data)
-                elif key == "Event":
-                    self.dji.add_event(data)
-                elif key == "Place":
-                    self.dji.add_place(data)
-                elif key == "Repository":
-                    self.dji.add_repository(data)
-                elif key == "Citation":
-                    self.dji.add_citation(data)
-                elif key == "Source":
-                    self.dji.add_source(data)
-                elif key == "Note":
-                    self.dji.add_note(data)
-                elif key == "Media":
-                    self.dji.add_media(data)
-                elif key == "Tag":
-                    self.dji.add_tag(data)
-        for key in db._tables.keys():
-            cursor = db._tables[key]["cursor_func"]
-            for (handle, data) in cursor():
-                if key == "Person":
-                    self.dji.add_person_detail(data)
-                elif key == "Family":
-                    self.dji.add_family_detail(data)
-                elif key == "Event":
-                    self.dji.add_event_detail(data)
-                elif key == "Place":
-                    self.dji.add_place_detail(data)
-                elif key == "Repository":
-                    self.dji.add_repository_detail(data)
-                elif key == "Citation":
-                    self.dji.add_citation_detail(data)
-                elif key == "Source":
-                    self.dji.add_source_detail(data)
-                elif key == "Note":
-                    self.dji.add_note_detail(data)
-                elif key == "Media":
-                    self.dji.add_media_detail(data)
-                elif key == "Tag":
-                    self.dji.add_tag_detail(data)
-            # Next we add the links:
-        self.dji.update_publics()
-
-    def get_from_name_and_handle(self, table_name, handle):
-        """
-        Returns a gen.lib object (or None) given table_name and
-        handle.
-
-        Examples:
-
-        >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008")
-        >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23")
-        """
-        if table_name in self._tables:
-            return self._tables[table_name]["handle_func"](handle)
-        return None
-
-    def is_empty(self):
-        """
-        Is the database empty?
-        """
-        return (self.get_number_of_people() == 0 and
-                self.get_number_of_events() == 0 and
-                self.get_number_of_places() == 0 and
-                self.get_number_of_tags() == 0 and
-                self.get_number_of_families() == 0 and
-                self.get_number_of_notes() == 0 and
-                self.get_number_of_citations() == 0 and
-                self.get_number_of_sources() == 0 and
-                self.get_number_of_media_objects() == 0 and
-                self.get_number_of_repositories() == 0)
-                
-    def set_prefixes(self, person, media, family, source, citation,
-                     place, event, repository, note):
-        self.set_person_id_prefix(person)
-        self.set_object_id_prefix(media)
-        self.set_family_id_prefix(family)
-        self.set_source_id_prefix(source)
-        self.set_citation_id_prefix(citation)
-        self.set_place_id_prefix(place)
-        self.set_event_id_prefix(event)
-        self.set_repository_id_prefix(repository)
-        self.set_note_id_prefix(note)
-
-    def has_changed(self):
-        return False
-
-    def find_backlink_handles(self, handle, include_classes=None):
-        ## FIXME: figure out how to get objects that refer
-        ## to this handle
-        return []
-
-    def get_note_bookmarks(self):
-        return self.note_bookmarks
-
-    def get_media_bookmarks(self):
-        return self.media_bookmarks
-
-    def get_repo_bookmarks(self):
-        return self.repo_bookmarks
-
-    def get_citation_bookmarks(self):
-        return self.citation_bookmarks
-
-    def get_source_bookmarks(self):
-        return self.source_bookmarks
-
-    def get_place_bookmarks(self):
-        return self.place_bookmarks
-
-    def get_event_bookmarks(self):
-        return self.event_bookmarks
-
-    def get_bookmarks(self):
-        return self.bookmarks
-
-    def get_family_bookmarks(self):
-        return self.family_bookmarks
-
-    def get_save_path(self):
-        return self._directory
-
-    def set_save_path(self, directory):
-        self._directory = directory
-        self.full_name = os.path.abspath(self._directory)
-        self.path = self.full_name
-        self.brief_name = os.path.basename(self._directory)
-
-    ## Get types:
-    def get_event_attribute_types(self):
-        """
-        Return a list of all Attribute types assocated with Event instances
-        in the database.
-        """
-        return list(self.event_attributes)
-
-    def get_event_types(self):
-        """
-        Return a list of all event types in the database.
-        """
-        return list(self.event_names)
-
-    def get_person_event_types(self):
-        """
-        Deprecated:  Use get_event_types
-        """
-        return list(self.event_names)
-
-    def get_person_attribute_types(self):
-        """
-        Return a list of all Attribute types assocated with Person instances 
-        in the database.
-        """
-        return list(self.individual_attributes)
-
-    def get_family_attribute_types(self):
-        """
-        Return a list of all Attribute types assocated with Family instances 
-        in the database.
-        """
-        return list(self.family_attributes)
-
-    def get_family_event_types(self):
-        """
-        Deprecated:  Use get_event_types
-        """
-        return list(self.event_names)
-
-    def get_media_attribute_types(self):
-        """
-        Return a list of all Attribute types assocated with Media and MediaRef 
-        instances in the database.
-        """
-        return list(self.media_attributes)
-
-    def get_family_relation_types(self):
-        """
-        Return a list of all relationship types assocated with Family
-        instances in the database.
-        """
-        return list(self.family_rel_types)
-
-    def get_child_reference_types(self):
-        """
-        Return a list of all child reference types assocated with Family
-        instances in the database.
-        """
-        return list(self.child_ref_types)
-
-    def get_event_roles(self):
-        """
-        Return a list of all custom event role names assocated with Event
-        instances in the database.
-        """
-        return list(self.event_role_names)
-
-    def get_name_types(self):
-        """
-        Return a list of all custom names types assocated with Person
-        instances in the database.
-        """
-        return list(self.name_types)
-
-    def get_origin_types(self):
-        """
-        Return a list of all custom origin types assocated with Person/Surname
-        instances in the database.
-        """
-        return list(self.origin_types)
-
-    def get_repository_types(self):
-        """
-        Return a list of all custom repository types assocated with Repository 
-        instances in the database.
-        """
-        return list(self.repository_types)
-
-    def get_note_types(self):
-        """
-        Return a list of all custom note types assocated with Note instances 
-        in the database.
-        """
-        return list(self.note_types)
-
-    def get_source_attribute_types(self):
-        """
-        Return a list of all Attribute types assocated with Source/Citation
-        instances in the database.
-        """
-        return list(self.source_attributes)
-
-    def get_source_media_types(self):
-        """
-        Return a list of all custom source media types assocated with Source 
-        instances in the database.
-        """
-        return list(self.source_media_types)
-
-    def get_url_types(self):
-        """
-        Return a list of all custom names types assocated with Url instances 
-        in the database.
-        """
-        return list(self.url_types)
-
-    def get_place_types(self):
-        """
-        Return a list of all custom place types assocated with Place instances
-        in the database.
-        """
-        return list(self.place_types)
-
-    def get_default_handle(self):
-        people = self.dji.Person.all()
-        if people.count() > 0:
-            return people[0].handle
-        return None
-
-    def close(self):
-        if self._directory:
-            filename = os.path.join(self._directory, "meta_data.db")
-            touch(filename)
-
-    def get_surname_list(self):
-        return []
-
-    def is_open(self):
-        return True
-
-    def get_table_names(self):
-        """Return a list of valid table names."""
-        return list(self._tables.keys())
-
-    def find_initial_person(self):
-        return self.get_default_person()
-
-    # Removals:
-    def remove_person(self, handle, txn):
-        self.dji.Person.filter(handle=handle)[0].delete()
-        self.emit("person-delete", ([handle],))
-
-    def remove_source(self, handle, transaction):
-        self.dji.Source.filter(handle=handle)[0].delete()
-        self.emit("source-delete", ([handle],))
-
-    def remove_citation(self, handle, transaction):
-        self.dji.Citation.filter(handle=handle)[0].delete()
-        self.emit("citation-delete", ([handle],))
-
-    def remove_event(self, handle, transaction):
-        self.dji.Event.filter(handle=handle)[0].delete()
-        self.emit("event-delete", ([handle],))
-
-    def remove_object(self, handle, transaction):
-        self.dji.Media.filter(handle=handle)[0].delete()
-        self.emit("media-delete", ([handle],))
-
-    def remove_place(self, handle, transaction):
-        self.dji.Place.filter(handle=handle)[0].delete()
-        self.emit("place-delete", ([handle],))
-
-    def remove_family(self, handle, transaction):
-        self.dji.Family.filter(handle=handle)[0].delete()
-        self.emit("family-delete", ([handle],))
-
-    def remove_repository(self, handle, transaction):
-        self.dji.Repository.filter(handle=handle)[0].delete()
-        self.emit("repository-delete", ([handle],))
-
-    def remove_note(self, handle, transaction):
-        self.dji.Note.filter(handle=handle)[0].delete()
-        self.emit("note-delete", ([handle],))
-
-    def remove_tag(self, handle, transaction):
-        self.dji.Tag.filter(handle=handle)[0].delete()
-        self.emit("tag-delete", ([handle],))
-
-    def remove_from_surname_list(self, person):
-        ## FIXME
-        ## called by a complete commit_person
-        pass
-    
-    ## was missing
-
-    def find_place_child_handles(self, handle):
-        pass
-
-    def get_cursor(self, table, txn=None, update=False, commit=False):
-        pass
-
-    def get_number_of_records(self, table):
-        pass
-
-    def get_place_parent_cursor(self):
-        pass
-
-    def get_place_tree_cursor(self):
-        pass
-
-    def get_table_metadata(self, table_name):
-        """Return the metadata for a valid table name."""
-        if table_name in self._tables:
-            return self._tables[table_name]
-        return None
-
-    def get_transaction_class(self):
-        pass
-
-    def undo(self, update_history=True):
-        # FIXME:
-        return self.undodb.undo(update_history)
-
-    def redo(self, update_history=True):
-        # FIXME:
-        return self.undodb.redo(update_history)
-
-    def backup(self):
-        pass
-
-    def restore(self):
-        pass
-
-    def write_version(self, directory):
-        """Write files for a newly created DB."""
-        versionpath = os.path.join(directory, str(DBBACKEND))
-        _LOG.debug("Write database backend file to 'djangodb'")
-        with open(versionpath, "w") as version_file:
-            version_file.write("djangodb")
-        # Write default_settings, sqlite.db
-        defaults = os.path.join(os.path.dirname(os.path.abspath(__file__)),
-                                "django_support", "defaults")
-        _LOG.debug("Copy defaults from: " + defaults)
-        for filename in os.listdir(defaults):
-            fullpath = os.path.abspath(os.path.join(defaults, filename))
-            shutil.copy2(fullpath, directory)
-        # force load, to get all modules loaded because of reset issue
-        self.load(directory)
-
-    def report_bm_change(self):
-        """
-        Add 1 to the number of bookmark changes during this session.
-        """
-        self._bm_changes += 1
-
-    def db_has_bm_changes(self):
-        """
-        Return whethere there were bookmark changes during the session.
-        """
-        return self._bm_changes > 0
-
-    def get_summary(self):
-        """
-        Returns dictionary of summary item.
-        Should include, if possible:
-
-        _("Number of people")
-        _("Version")
-        _("Schema version")
-        """
-        return {
-            _("Number of people"): self.get_number_of_people(),
-        }
-
-    def get_dbname(self):
-        """
-        In Django, the database is in a text file at the path
-        """
-        filepath = os.path.join(self._directory, "name.txt")
-        try:
-            name_file = open(filepath, "r")
-            name = name_file.readline().strip()
-            name_file.close()
-        except (OSError, IOError) as msg:
-            _LOG.error(str(msg))
-            name = None
-        return name
-
-    def reindex_reference_map(self):
-        pass
-
-    def rebuild_secondary(self, update):
-        pass