From 8c36ed6a8e526c188249ab6d064de418665776fe Mon Sep 17 00:00:00 2001 From: Tim G L Lyons Date: Fri, 30 Sep 2011 21:34:21 +0000 Subject: [PATCH] Add MergeCitations tool. Add note to upgrade information dialogue box to suggest running the tool. Fixes to Person and Family for get citation child list for merging citations. svn: r18228 --- src/gen/db/upgrade.py | 4 + src/gen/lib/family.py | 3 +- src/gen/lib/person.py | 3 +- src/plugins/tool/MergeCitations.py | 288 ++++++++++++++++++++++++++ src/plugins/tool/mergecitations.glade | 255 +++++++++++++++++++++++ src/plugins/tool/tools.gpr.py | 24 +++ 6 files changed, 573 insertions(+), 4 deletions(-) create mode 100644 src/plugins/tool/MergeCitations.py create mode 100644 src/plugins/tool/mergecitations.glade diff --git a/src/gen/db/upgrade.py b/src/gen/db/upgrade.py index e0c1f1d15..3afb37e5a 100644 --- a/src/gen/db/upgrade.py +++ b/src/gen/db/upgrade.py @@ -384,6 +384,10 @@ def gramps_upgrade_16(self): txt += key2string[key] % data_upgradeobject[key2data[key]] except: txt += key2string[key] + txt += _("\n\nYou may want to run\n" + "Tools -> Family Tree Processing -> Merge\n" + "in order to merge citations that contain similar\n" + "information") InfoDialog(_('Upgrade Statistics'), txt) def upgrade_media_list_16(self, media_list): diff --git a/src/gen/lib/family.py b/src/gen/lib/family.py index 6b5dd4000..c0d9d9aca 100644 --- a/src/gen/lib/family.py +++ b/src/gen/lib/family.py @@ -283,8 +283,7 @@ class Family(CitationBase, NoteBase, MediaBase, AttributeBase, LdsOrdBase, :rtype: list """ check_list = self.media_list + self.attribute_list + \ - self.lds_ord_list + self.child_ref_list + \ - self.event_ref_list + self.lds_ord_list + self.child_ref_list return check_list def get_note_child_list(self): diff --git a/src/gen/lib/person.py b/src/gen/lib/person.py index 5ce18323d..addcf4cff 100644 --- a/src/gen/lib/person.py +++ b/src/gen/lib/person.py @@ -389,8 +389,7 @@ class Person(CitationBase, NoteBase, AttributeBase, MediaBase, self.address_list + self.attribute_list + self.lds_ord_list + - self.person_ref_list + - self.event_ref_list + self.person_ref_list ) def get_note_child_list(self): diff --git a/src/plugins/tool/MergeCitations.py b/src/plugins/tool/MergeCitations.py new file mode 100644 index 000000000..8f4d54f41 --- /dev/null +++ b/src/plugins/tool/MergeCitations.py @@ -0,0 +1,288 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2008 Brian G. Matherly +# Copyright (C) 2010 Jakim Friant +# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +"""Tools/Family Tree Processing/MergeCitations""" + +#------------------------------------------------------------------------ +# +# Python modules +# +#------------------------------------------------------------------------ +import logging +LOG = logging.getLogger(".citation") + +#------------------------------------------------------------------------- +# +# GNOME libraries +# +#------------------------------------------------------------------------- +import gtk + +#------------------------------------------------------------------------- +# +# GRAMPS modules +# +#------------------------------------------------------------------------- +from Utils import confidence +import const +from gui.utils import ProgressMeter +from gui.plug import tool +from QuestionDialog import OkDialog +import GrampsDisplay +import DateHandler +import ManagedWindow +from gen.ggettext import sgettext as _ +from glade import Glade +from gen.db import DbTxn +from gen.lib import (Person, Family, Event, Place, MediaObject, Citation) +from Errors import MergeError + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- +ALL_FIELDS = 0 +IGNORE_DATE = 1 +IGNORE_CONFIDENCE = 2 +IGNORE_BOTH = 3 + +_val2label = { + ALL_FIELDS : _("Match on Page/Volume, Date and Confidence"), + IGNORE_DATE : _("Ignore Date"), + IGNORE_CONFIDENCE : _("Ignore Confidence"), + IGNORE_BOTH : _("Ignore Date and Confidence") + } + +WIKI_HELP_PAGE = '%s_-_Tools' % const.URL_MANUAL_PAGE +WIKI_HELP_SEC = _('manual|Merge citations...') + +#------------------------------------------------------------------------- +# +# The Actual tool. +# +#------------------------------------------------------------------------- +class MergeCitations(tool.BatchTool,ManagedWindow.ManagedWindow): + + def __init__(self, dbstate, uistate, options_class, name, callback=None): + + ManagedWindow.ManagedWindow.__init__(self, uistate, [], self.__class__) + self.dbstate = dbstate + self.set_window(gtk.Window(), gtk.Label(), '') + + tool.BatchTool.__init__(self, dbstate, options_class, name) + + if not self.fail: + uistate.set_busy_cursor(True) + self.run() + uistate.set_busy_cursor(False) + + def run(self): + + top = Glade(toplevel="mergecitations") + + # retrieve options + fields = self.options.handler.options_dict['fields'] + dont_merge_notes = self.options.handler.options_dict['dont_merge_notes'] + + my_menu = gtk.ListStore(str, object) + for val in sorted(_val2label): + my_menu.append([_val2label[val], val]) + + self.notes_obj = top.get_object("notes") + self.notes_obj.set_active(dont_merge_notes) + self.notes_obj.show() + + self.menu = top.get_object("menu") + self.menu.set_model(my_menu) + self.menu.set_active(fields) + + window = top.toplevel + window.show() +# self.set_window(window, top.get_object('title'), +# _('Merge citations')) + self.set_window(window, top.get_object('title2'), + "Notes, media objects and data-items of matching " + "citations will be combined.") + + top.connect_signals({ + "on_merge_ok_clicked" : self.on_merge_ok_clicked, + "destroy_passed_object" : self.cancel, + "on_help_clicked" : self.on_help_clicked, + "on_delete_merge_event" : self.close, + "on_delete_event" : self.close, + }) + + self.show() + + def cancel(self, obj): + """ + on cancel, update the saved values of the options. + """ + fields = self.menu.get_model()[self.menu.get_active()][1] + dont_merge_notes = int(self.notes_obj.get_active()) + LOG.debug("cancel fields %d dont_merge_notes %d" % + (fields, dont_merge_notes)) + + self.options.handler.options_dict['fields'] = fields + self.options.handler.options_dict['dont_merge_notes'] = dont_merge_notes + # Save options + self.options.handler.save_options() + + self.close(obj) + + def build_menu_names(self, obj): + return (_("Tool settings"),_("Merge citations tool")) + + def on_help_clicked(self, obj): + """Display the relevant portion of GRAMPS manual""" + + GrampsDisplay.help(WIKI_HELP_PAGE , WIKI_HELP_SEC) + + def on_merge_ok_clicked(self, obj): + """ + Performs the actual merge of citations + (Derived from ExtractCity) + """ + fields = self.menu.get_model()[self.menu.get_active()][1] + dont_merge_notes = int(self.notes_obj.get_active()) + LOG.debug("fields %d dont_merge_notes %d" % (fields, dont_merge_notes)) + + self.options.handler.options_dict['fields'] = fields + self.options.handler.options_dict['dont_merge_notes'] = dont_merge_notes + # Save options + self.options.handler.save_options() + + self.progress = ProgressMeter(_('Checking Sources'), '') + self.progress.set_pass(_('Looking for citation fields'), + self.db.get_number_of_citations()) + + db = self.dbstate.db + + db.disable_signals() + num_merges = 0 + with DbTxn(_("Merge Citation"), db) as trans: + for handle in db.iter_source_handles(): + dict = {} + citation_handle_list = list(db.find_backlink_handles(handle)) + for (class_name, citation_handle) in citation_handle_list: + if class_name <> Citation.__name__: + raise MergeError("Encountered an object of type %s " + "that has a citation reference." % class_name) + + citation = db.get_citation_from_handle(citation_handle) + key = citation.get_page() + if fields <> IGNORE_DATE and fields <> IGNORE_BOTH: + key += "\n" + DateHandler.get_date(citation) + if fields <> IGNORE_CONFIDENCE and fields <> IGNORE_BOTH: + key += "\n" + \ + confidence[citation.get_confidence_level()] + if key in dict and \ + (not dont_merge_notes or len(citation.note_list) == 0): + citation_match_handle = dict[key] + citation_match = \ + db.get_citation_from_handle(citation_match_handle) + self.Merge(db, citation_match, citation, trans) + num_merges += 1 + else: + dict[key] = citation_handle + self.progress.step() + db.enable_signals() + db.request_rebuild() + self.progress.close() + OkDialog( + _("Number of merges done"), + _("%d citations merges" % num_merges), + parent=self.window) + + def Merge (self, db, citation1, citation2, trans): + """ + Merges two citations into a single citation. + """ + new_handle = citation1.get_handle() + old_handle = citation2.get_handle() + + citation1.merge(citation2) + + db.commit_citation(citation1, trans) + for (class_name, handle) in db.find_backlink_handles( + old_handle): + if class_name == Person.__name__: + person = db.get_person_from_handle(handle) + assert(person.has_citation_reference(old_handle)) + person.replace_citation_references(old_handle, new_handle) + db.commit_person(person, trans) + elif class_name == Family.__name__: + family = db.get_family_from_handle(handle) + assert(family.has_citation_reference(old_handle)) + family.replace_citation_references(old_handle, new_handle) + db.commit_family(family, trans) + elif class_name == Event.__name__: + event = db.get_event_from_handle(handle) + assert(event.has_citation_reference(old_handle)) + event.replace_citation_references(old_handle, new_handle) + db.commit_event(event, trans) + elif class_name == Place.__name__: + place = db.get_place_from_handle(handle) + assert(place.has_citation_reference(old_handle)) + place.replace_citation_references(old_handle, new_handle) + db.commit_place(place, trans) + elif class_name == MediaObject.__name__: + obj = db.get_object_from_handle(handle) + assert(obj.has_citation_reference(old_handle)) + obj.replace_citation_references(old_handle, new_handle) + db.commit_media_object(obj, trans) + else: + raise MergeError("Encountered an object of type %s that has " + "a citation reference." % class_name) + db.remove_citation(old_handle, trans) + +#------------------------------------------------------------------------ +# +# +# +#------------------------------------------------------------------------ +class MergeCitationsOptions(tool.ToolOptions): + """ + Defines options and provides handling interface. + """ + + def __init__(self, name,person_id=None): + tool.ToolOptions.__init__(self, name,person_id) + + # Options specific for this report + self.options_dict = { + 'fields' : 1, + 'dont_merge_notes' : 0, + } + self.options_help = { + 'dont_merge_notes' : + ("=0/1","Whether to merge citations if they have notes", + ["Merge citations with notes", + "Do not merge citations with notes"], + False), + 'fields' : ("=num","Threshold for matching", + "Integer number") + } diff --git a/src/plugins/tool/mergecitations.glade b/src/plugins/tool/mergecitations.glade new file mode 100644 index 000000000..d3b747c77 --- /dev/null +++ b/src/plugins/tool/mergecitations.glade @@ -0,0 +1,255 @@ + + + + + + True + + + True + 12 + + + True + center + + + False + False + 6 + 0 + + + + + True + 10 + Please be patient. This may take a while. + center + True + + + False + False + 20 + 1 + + + + + True + + + True + 0.10000000149 + + + 20 + 0 + + + + + False + False + 2 + + + + + + + + + + + + + 350 + dialog + False + + + + True + 8 + + + True + 6 + 6 + + + True + center + + + False + False + 6 + 0 + + + + + True + 12 + 5 + 2 + 12 + 6 + + + True + 0 + <b>Match Threshold</b> + True + + + 2 + GTK_FILL + + + + + + True + 0 + <b>Options</b> + True + + + 2 + 3 + 4 + GTK_FILL + + + + + + Don't merge if citation has notes + True + True + False + True + True + True + + + 1 + 2 + 4 + 5 + GTK_FILL + + + + + + True + liststore1 + + + + 0 + + + + + 1 + 2 + 1 + 2 + + + + + + + + + + + + + + + + + 1 + + + + + 1 + + + + + True + end + + + gtk-cancel + True + True + True + False + True + + + + False + False + 0 + + + + + gtk-ok + True + True + True + False + True + + + + False + False + 1 + + + + + gtk-help + True + True + True + False + True + + + + False + False + 2 + + + + + False + end + 0 + + + + + + button12 + button10 + button14 + + + diff --git a/src/plugins/tool/tools.gpr.py b/src/plugins/tool/tools.gpr.py index bf46d6755..9c58033a3 100644 --- a/src/plugins/tool/tools.gpr.py +++ b/src/plugins/tool/tools.gpr.py @@ -517,3 +517,27 @@ toolclass = 'Verify', optionclass = 'VerifyOptions', tool_modes = [TOOL_MODE_GUI, TOOL_MODE_CLI] ) + +#------------------------------------------------------------------------ +# +# Merge citations +# +#------------------------------------------------------------------------ + +register(TOOL, +id = 'mergecitations', +name = _("Merge Citations"), +description = _("Searches the entire database, looking for " + "citations that have the same Volume/Page, Date and Confidence."), +version = '1.0', +gramps_target_version = '3.4', +status = STABLE, +fname = 'MergeCitations.py', +authors = ["Tim G L Lyons"], +authors_email = ["gramps-project.org"], +category = TOOL_DBPROC, +toolclass = 'MergeCitations', +optionclass = 'MergeCitationsOptions', +tool_modes = [TOOL_MODE_GUI] + ) +