From 6da4a0037551d2f5b43667ea77fc9fb9fe7fd200 Mon Sep 17 00:00:00 2001 From: Vassilii Khachaturov Date: Fri, 30 Aug 2013 19:41:29 +0000 Subject: [PATCH] 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 --- src/Merge/test/merge_ref_test.py | 15 +++++++++ src/gen/lib/citation.py | 14 +++++++- src/gen/lib/citationbase.py | 53 +++++++++++++++++++++++++++++ src/gen/lib/eventref.py | 45 ++----------------------- src/gen/lib/repo.py | 57 ++------------------------------ 5 files changed, 87 insertions(+), 97 deletions(-) diff --git a/src/Merge/test/merge_ref_test.py b/src/Merge/test/merge_ref_test.py index 9b70136c6..a2f1b344d 100644 --- a/src/Merge/test/merge_ref_test.py +++ b/src/Merge/test/merge_ref_test.py @@ -42,6 +42,9 @@ import gen.lib from gen.lib import Name, Surname 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): """Context manager that creates a deep copy of an libxml-xml document.""" def __init__(self, xmldoc): @@ -72,6 +75,8 @@ class XpathContext(object): self.ctxt.xpathFreeContext() return False +@unittest.skipUnless(HAS_CLIMERGE and HAS_EXPORTRAW, + 'These tests need the 3rd-party plugins "CliMerge" and "ExportRaw".') class BaseMergeCheck(unittest.TestCase): def base_setup(self): """Set up code needed by all tests.""" @@ -136,6 +141,9 @@ class BaseMergeCheck(unittest.TestCase): if test_error_str: self.assertIn(test_error_str, err_str) return + else: + if "Traceback (most recent call last):" in err_str: + raise Exception(err_str) if debug: print('input :', self.canonicalize(input_doc)) print('result:', self.canonicalize(result_str)) @@ -2223,4 +2231,11 @@ class FamilyMergeCheck(BaseMergeCheck): 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() diff --git a/src/gen/lib/citation.py b/src/gen/lib/citation.py index 73dd431c0..979bc959e 100644 --- a/src/gen/lib/citation.py +++ b/src/gen/lib/citation.py @@ -43,13 +43,15 @@ from gen.lib.primaryobj import PrimaryObject from gen.lib.mediabase import MediaBase from gen.lib.notebase import NoteBase from gen.lib.datebase import DateBase +from gen.lib.citationbase import IndirectCitationBase #------------------------------------------------------------------------- # # 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. @@ -192,6 +194,16 @@ class Citation(MediaBase, NoteBase, PrimaryObject, DateBase): """ 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): """ Return the list of child objects which may, directly or through diff --git a/src/gen/lib/citationbase.py b/src/gen/lib/citationbase.py index 58332538d..71f48291d 100644 --- a/src/gen/lib/citationbase.py +++ b/src/gen/lib/citationbase.py @@ -234,3 +234,56 @@ class CitationBase(object): for item in self.get_citation_child_list(): 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) diff --git a/src/gen/lib/eventref.py b/src/gen/lib/eventref.py index e5f97b1b4..82a12dc58 100644 --- a/src/gen/lib/eventref.py +++ b/src/gen/lib/eventref.py @@ -38,13 +38,15 @@ from gen.lib.attrbase import AttributeBase from gen.lib.refbase import RefBase from gen.lib.eventroletype import EventRoleType from gen.lib.const import IDENTICAL, EQUAL, DIFFERENT +from gen.lib.citationbase import IndirectCitationBase #------------------------------------------------------------------------- # # Event References for Person/Family # #------------------------------------------------------------------------- -class EventRef(SecondaryObject, PrivacyBase, NoteBase, AttributeBase, RefBase): +class EventRef(IndirectCitationBase, + SecondaryObject, PrivacyBase, NoteBase, AttributeBase, RefBase): """ Event reference class. @@ -152,47 +154,6 @@ class EventRef(SecondaryObject, PrivacyBase, NoteBase, AttributeBase, RefBase): """ 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): """ Return if this eventref is equivalent, that is agrees in handle and diff --git a/src/gen/lib/repo.py b/src/gen/lib/repo.py index a684d2dbe..fde399acc 100644 --- a/src/gen/lib/repo.py +++ b/src/gen/lib/repo.py @@ -36,13 +36,15 @@ from gen.lib.notebase import NoteBase from gen.lib.addressbase import AddressBase from gen.lib.urlbase import UrlBase from gen.lib.repotype import RepositoryType +from gen.lib.citationbase import IndirectCitationBase #------------------------------------------------------------------------- # # Repository class # #------------------------------------------------------------------------- -class Repository(NoteBase, AddressBase, UrlBase, PrimaryObject): +class Repository(NoteBase, AddressBase, UrlBase, IndirectCitationBase, + PrimaryObject): """A location where collections of Sources are found.""" def __init__(self): @@ -140,59 +142,6 @@ class Repository(NoteBase, AddressBase, UrlBase, PrimaryObject): """ 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): """ Merge the content of acquisition into this repository.