6960: Error merging citations

Back-port my work on this bug from gramps40.
Unfortunately, the tests on gramps34 still fail (they pass
on gramps40/trunk), but the repro steps marked by Nick
in the bug succeed, so I shall stop here :)

svn: r22965
This commit is contained in:
Vassilii Khachaturov 2013-08-30 19:41:29 +00:00
parent b0d5343d24
commit 6da4a00375
5 changed files with 87 additions and 97 deletions

View File

@ -42,6 +42,9 @@ import gen.lib
from gen.lib import Name, Surname from gen.lib import Name, Surname
from gen.ggettext import sgettext as _ from gen.ggettext import sgettext as _
HAS_CLIMERGE = os.path.isdir(os.path.join(USER_PLUGINS, 'CliMerge'))
HAS_EXPORTRAW = os.path.isdir(os.path.join(USER_PLUGINS, 'ExportRaw'))
class CopiedDoc(object): class CopiedDoc(object):
"""Context manager that creates a deep copy of an libxml-xml document.""" """Context manager that creates a deep copy of an libxml-xml document."""
def __init__(self, xmldoc): def __init__(self, xmldoc):
@ -72,6 +75,8 @@ class XpathContext(object):
self.ctxt.xpathFreeContext() self.ctxt.xpathFreeContext()
return False return False
@unittest.skipUnless(HAS_CLIMERGE and HAS_EXPORTRAW,
'These tests need the 3rd-party plugins "CliMerge" and "ExportRaw".')
class BaseMergeCheck(unittest.TestCase): class BaseMergeCheck(unittest.TestCase):
def base_setup(self): def base_setup(self):
"""Set up code needed by all tests.""" """Set up code needed by all tests."""
@ -136,6 +141,9 @@ class BaseMergeCheck(unittest.TestCase):
if test_error_str: if test_error_str:
self.assertIn(test_error_str, err_str) self.assertIn(test_error_str, err_str)
return return
else:
if "Traceback (most recent call last):" in err_str:
raise Exception(err_str)
if debug: if debug:
print('input :', self.canonicalize(input_doc)) print('input :', self.canonicalize(input_doc))
print('result:', self.canonicalize(result_str)) print('result:', self.canonicalize(result_str))
@ -2223,4 +2231,11 @@ class FamilyMergeCheck(BaseMergeCheck):
if __name__ == "__main__": if __name__ == "__main__":
import sys
if not HAS_CLIMERGE:
print('This program needs the third party "CliMerge" plugin.', file=sys.stderr)
sys.exit(1)
if not HAS_EXPORTRAW:
print('This program needs the third party "ExportRaw" plugin.', file=sys.stderr)
sys.exit(1)
unittest.main() unittest.main()

View File

@ -43,13 +43,15 @@ from gen.lib.primaryobj import PrimaryObject
from gen.lib.mediabase import MediaBase from gen.lib.mediabase import MediaBase
from gen.lib.notebase import NoteBase from gen.lib.notebase import NoteBase
from gen.lib.datebase import DateBase from gen.lib.datebase import DateBase
from gen.lib.citationbase import IndirectCitationBase
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# #
# Citation class # Citation class
# #
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
class Citation(MediaBase, NoteBase, PrimaryObject, DateBase): class Citation(MediaBase, NoteBase, IndirectCitationBase,
PrimaryObject, DateBase):
""" """
A record of a citation of a source of information. A record of a citation of a source of information.
@ -192,6 +194,16 @@ class Citation(MediaBase, NoteBase, PrimaryObject, DateBase):
""" """
return self.media_list return self.media_list
def get_citation_child_list(self):
"""
Return the list of child secondary objects that may refer citations.
:returns: Returns the list of child secondary child objects that may
refer citations.
:rtype: list
"""
return self.media_list
def get_handle_referents(self): def get_handle_referents(self):
""" """
Return the list of child objects which may, directly or through Return the list of child objects which may, directly or through

View File

@ -234,3 +234,56 @@ class CitationBase(object):
for item in self.get_citation_child_list(): for item in self.get_citation_child_list():
item.replace_citation_references(old_handle, new_handle) item.replace_citation_references(old_handle, new_handle)
class IndirectCitationBase(object):
"""
Citation management logic for objects that don't have citations
for the primary objects, but only for the child (secondary) ones.
The derived class must implement get_citation_child_list method
to return the list of child secondary objects that may refer
citations.
Note: for most objects, this functionality is inherited from
CitationBase, which checks both the object and the child objects.
"""
def has_citation_reference(self, citation_handle):
"""
Return True if any of the child objects has reference to this citation
handle.
:param citation_handle: The citation handle to be checked.
:type citation_handle: str
:returns: Returns whether any of it's child objects has reference to
this citation handle.
:rtype: bool
"""
for item in self.get_citation_child_list():
if item.has_citation_reference(citation_handle):
return True
return False
def replace_citation_references(self, old_handle, new_handle):
"""
Replace references to citation handles in
all child objects and merge equivalent entries.
:param old_handle: The citation handle to be replaced.
:type old_handle: str
:param new_handle: The citation handle to replace the old one with.
:type new_handle: str
"""
for item in self.get_citation_child_list():
item.replace_citation_references(old_handle, new_handle)
def remove_citation_references(self, citation_handle_list):
"""
Remove references to all citation handles in the list in all child
objects.
:param citation_handle_list: The list of citation handles to be removed.
:type citation_handle_list: list
"""
for item in self.get_citation_child_list():
item.remove_citation_references(citation_handle_list)

View File

@ -38,13 +38,15 @@ from gen.lib.attrbase import AttributeBase
from gen.lib.refbase import RefBase from gen.lib.refbase import RefBase
from gen.lib.eventroletype import EventRoleType from gen.lib.eventroletype import EventRoleType
from gen.lib.const import IDENTICAL, EQUAL, DIFFERENT from gen.lib.const import IDENTICAL, EQUAL, DIFFERENT
from gen.lib.citationbase import IndirectCitationBase
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# #
# Event References for Person/Family # Event References for Person/Family
# #
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
class EventRef(SecondaryObject, PrivacyBase, NoteBase, AttributeBase, RefBase): class EventRef(IndirectCitationBase,
SecondaryObject, PrivacyBase, NoteBase, AttributeBase, RefBase):
""" """
Event reference class. Event reference class.
@ -152,47 +154,6 @@ class EventRef(SecondaryObject, PrivacyBase, NoteBase, AttributeBase, RefBase):
""" """
return self.get_citation_child_list() return self.get_citation_child_list()
def has_citation_reference(self, citation_handle) :
"""
Return True if any of the child objects has reference to this citation
handle.
:param citation_handle: The citation handle to be checked.
:type citation_handle: str
:returns: Returns whether any of it's child objects has reference to
this citation handle.
:rtype: bool
"""
for item in self.get_citation_child_list():
if item.has_citation_reference(citation_handle):
return True
return False
def remove_citation_references(self, citation_handle_list):
"""
Remove references to all citation handles in the list in all child
objects.
:param citation_handle_list: The list of citation handles to be removed.
:type citation_handle_list: list
"""
for item in self.get_citation_child_list():
item.remove_citation_references(citation_handle_list)
def replace_citation_references(self, old_handle, new_handle):
"""
Replace references to citation handles in the list in this object and
all child objects and merge equivalent entries.
:param old_handle: The citation handle to be replaced.
:type old_handle: str
:param new_handle: The citation handle to replace the old one with.
:type new_handle: str
"""
for item in self.get_citation_child_list():
item.replace_citation_references(old_handle, new_handle)
def is_equivalent(self, other): def is_equivalent(self, other):
""" """
Return if this eventref is equivalent, that is agrees in handle and Return if this eventref is equivalent, that is agrees in handle and

View File

@ -36,13 +36,15 @@ from gen.lib.notebase import NoteBase
from gen.lib.addressbase import AddressBase from gen.lib.addressbase import AddressBase
from gen.lib.urlbase import UrlBase from gen.lib.urlbase import UrlBase
from gen.lib.repotype import RepositoryType from gen.lib.repotype import RepositoryType
from gen.lib.citationbase import IndirectCitationBase
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# #
# Repository class # Repository class
# #
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
class Repository(NoteBase, AddressBase, UrlBase, PrimaryObject): class Repository(NoteBase, AddressBase, UrlBase, IndirectCitationBase,
PrimaryObject):
"""A location where collections of Sources are found.""" """A location where collections of Sources are found."""
def __init__(self): def __init__(self):
@ -140,59 +142,6 @@ class Repository(NoteBase, AddressBase, UrlBase, PrimaryObject):
""" """
return self.get_referenced_note_handles() return self.get_referenced_note_handles()
def has_citation_reference(self, citation_handle) :
"""
Return True if any of the child objects has reference to this citation
handle.
Note: for most objects, this is inherited from citationbase, which
checks both the object and the child objects. However, uniquely,
Repositories do not have citations for the primary object, but only for
child (secondary) objects. Hence, this function has to be implemented
directly in the primary object; it only checks the child objects.
:param citation_handle: The citation handle to be checked.
:type citation_handle: str
:returns: Returns whether any of it's child objects has reference to
this citation handle.
:rtype: bool
"""
for item in self.get_citation_child_list():
if item.has_citation_reference(citation_handle):
return True
return False
def remove_citation_references(self, citation_handle_list):
"""
Remove references to all citation handles in the list in all child
objects.
Note: the same comment about citationbase in has_citation_reference
applies here too.
:param citation_handle_list: The list of citation handles to be removed.
:type citation_handle_list: list
"""
for item in self.get_citation_child_list():
item.remove_citation_references(citation_handle_list)
def replace_citation_references(self, old_handle, new_handle):
"""
Replace references to citation handles in the list in this object and
all child objects and merge equivalent entries.
Note: the same comment about citationbase in has_citation_reference
applies here too.
:param old_handle: The citation handle to be replaced.
:type old_handle: str
:param new_handle: The citation handle to replace the old one with.
:type new_handle: str
"""
for item in self.get_citation_child_list():
item.replace_citation_references(old_handle, new_handle)
def merge(self, acquisition): def merge(self, acquisition):
""" """
Merge the content of acquisition into this repository. Merge the content of acquisition into this repository.