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
This commit is contained in:
Tim G L Lyons 2011-09-30 21:34:21 +00:00
parent afefdb09a7
commit 8c36ed6a8e
6 changed files with 573 additions and 4 deletions

View File

@ -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):

View File

@ -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):

View File

@ -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):

View File

@ -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")
}

View File

@ -0,0 +1,255 @@
<?xml version="1.0"?>
<interface>
<!-- interface-requires gtk+ 2.12 -->
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkWindow" id="message">
<property name="modal">True</property>
<child>
<object class="GtkVBox" id="vbox2">
<property name="visible">True</property>
<property name="border_width">12</property>
<child>
<object class="GtkLabel" id="title">
<property name="visible">True</property>
<property name="justify">center</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="padding">6</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label44">
<property name="visible">True</property>
<property name="ypad">10</property>
<property name="label" translatable="yes">Please be patient. This may take a while.</property>
<property name="justify">center</property>
<property name="wrap">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="padding">20</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox4">
<property name="visible">True</property>
<child>
<object class="GtkProgressBar" id="progressbar1">
<property name="visible">True</property>
<property name="pulse_step">0.10000000149</property>
</object>
<packing>
<property name="padding">20</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkListStore" id="liststore1">
<columns>
<!-- column-name gchararray1 -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkDialog" id="mergecitations">
<property name="default_width">350</property>
<property name="type_hint">dialog</property>
<property name="has_separator">False</property>
<signal name="delete_event" handler="on_delete_merge_event"/>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox4">
<property name="visible">True</property>
<property name="spacing">8</property>
<child>
<object class="GtkVBox" id="vbox6">
<property name="visible">True</property>
<property name="border_width">6</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="title2">
<property name="visible">True</property>
<property name="justify">center</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="padding">6</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkTable" id="table1">
<property name="visible">True</property>
<property name="border_width">12</property>
<property name="n_rows">5</property>
<property name="n_columns">2</property>
<property name="column_spacing">12</property>
<property name="row_spacing">6</property>
<child>
<object class="GtkLabel" id="label62">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">&lt;b&gt;Match Threshold&lt;/b&gt;</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="right_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label63">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">&lt;b&gt;Options&lt;/b&gt;</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="notes">
<property name="label" translatable="yes">Don't merge if citation has notes</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="menu">
<property name="visible">True</property>
<property name="model">liststore1</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area4">
<property name="visible">True</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="button12">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="destroy_passed_object" object="mergecitations"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button10">
<property name="label">gtk-ok</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_merge_ok_clicked" object="mergecitations"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button14">
<property name="label">gtk-help</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_help_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="0">button12</action-widget>
<action-widget response="0">button10</action-widget>
<action-widget response="-11">button14</action-widget>
</action-widgets>
</object>
</interface>

View File

@ -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]
)