1# 2# Gramps - a GTK+/GNOME based genealogy program 3# 4# Copyright (C) 2000-2007 Donald N. Allingham 5# Copyright (C) 2010 Michiel D. Nauta 6# Copyright (C) 2010,2017 Nick Hall 7# Copyright (C) 2011 Tim G L Lyons 8# 9# This program is free software; you can redistribute it and/or modify 10# it under the terms of the GNU General Public License as published by 11# the Free Software Foundation; either version 2 of the License, or 12# (at your option) any later version. 13# 14# This program is distributed in the hope that it will be useful, 15# but WITHOUT ANY WARRANTY; without even the implied warranty of 16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17# GNU General Public License for more details. 18# 19# You should have received a copy of the GNU General Public License 20# along with this program; if not, write to the Free Software 21# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22# 23 24""" 25Family object for Gramps. 26""" 27 28#------------------------------------------------------------------------- 29# 30# standard python modules 31# 32#------------------------------------------------------------------------- 33from warnings import warn 34import logging 35 36#------------------------------------------------------------------------- 37# 38# Gramps modules 39# 40#------------------------------------------------------------------------- 41from .primaryobj import PrimaryObject 42from .citationbase import CitationBase 43from .notebase import NoteBase 44from .mediabase import MediaBase 45from .attrbase import AttributeBase 46from .eventref import EventRef 47from .ldsordbase import LdsOrdBase 48from .tagbase import TagBase 49from .childref import ChildRef 50from .familyreltype import FamilyRelType 51from .const import IDENTICAL, EQUAL, DIFFERENT 52from ..const import GRAMPS_LOCALE as glocale 53_ = glocale.translation.gettext 54 55LOG = logging.getLogger(".citation") 56 57#------------------------------------------------------------------------- 58# 59# Family class 60# 61#------------------------------------------------------------------------- 62class Family(CitationBase, NoteBase, MediaBase, AttributeBase, LdsOrdBase, 63 PrimaryObject): 64 """ 65 The Family record is the Gramps in-memory representation of the 66 relationships between people. It contains all the information 67 related to the relationship. 68 69 Family objects are usually created in one of two ways. 70 71 1. Creating a new Family object, which is then initialized and 72 added to the database. 73 2. Retrieving an object from the database using the records 74 handle. 75 76 Once a Family object has been modified, it must be committed 77 to the database using the database object's commit_family function, 78 or the changes will be lost. 79 """ 80 81 def __init__(self): 82 """ 83 Create a new Family instance. 84 85 After initialization, most data items have empty or null values, 86 including the database handle. 87 """ 88 PrimaryObject.__init__(self) 89 CitationBase.__init__(self) 90 NoteBase.__init__(self) 91 MediaBase.__init__(self) 92 AttributeBase.__init__(self) 93 LdsOrdBase.__init__(self) 94 self.father_handle = None 95 self.mother_handle = None 96 self.child_ref_list = [] 97 self.type = FamilyRelType() 98 self.event_ref_list = [] 99 self.complete = 0 100 101 def serialize(self): 102 """ 103 Convert the data held in the event to a Python tuple that 104 represents all the data elements. 105 106 This method is used to convert the object into a form that can easily 107 be saved to a database. 108 109 These elements may be primitive Python types (string, integers), 110 complex Python types (lists or tuples, or Python objects. If the 111 target database cannot handle complex types (such as objects or 112 lists), the database is responsible for converting the data into 113 a form that it can use. 114 115 :returns: Returns a python tuple containing the data that should 116 be considered persistent. 117 :rtype: tuple 118 """ 119 return (self.handle, self.gramps_id, self.father_handle, 120 self.mother_handle, 121 [cr.serialize() for cr in self.child_ref_list], 122 self.type.serialize(), 123 [er.serialize() for er in self.event_ref_list], 124 MediaBase.serialize(self), 125 AttributeBase.serialize(self), 126 LdsOrdBase.serialize(self), 127 CitationBase.serialize(self), 128 NoteBase.serialize(self), 129 self.change, TagBase.serialize(self), self.private) 130 131 @classmethod 132 def get_schema(cls): 133 """ 134 Returns the JSON Schema for this class. 135 136 :returns: Returns a dict containing the schema. 137 :rtype: dict 138 """ 139 from .mediaref import MediaRef 140 from .ldsord import LdsOrd 141 from .childref import ChildRef 142 from .attribute import Attribute 143 return { 144 "type": "object", 145 "title": _("Family"), 146 "properties": { 147 "_class": {"enum": [cls.__name__]}, 148 "handle": {"type": "string", 149 "maxLength": 50, 150 "title": _("Handle")}, 151 "gramps_id": {"type": "string", 152 "title": _("Gramps ID")}, 153 "father_handle": {"type": ["string", "null"], 154 "maxLength": 50, 155 "title": _("Father")}, 156 "mother_handle": {"type": ["string", "null"], 157 "maxLength": 50, 158 "title": _("Mother")}, 159 "child_ref_list": {"type": "array", 160 "items": ChildRef.get_schema(), 161 "title": _("Children")}, 162 "type": FamilyRelType.get_schema(), 163 "event_ref_list": {"type": "array", 164 "items": EventRef.get_schema(), 165 "title": _("Events")}, 166 "media_list": {"type": "array", 167 "items": MediaRef.get_schema(), 168 "title": _("Media")}, 169 "attribute_list": {"type": "array", 170 "items": Attribute.get_schema(), 171 "title": _("Attributes")}, 172 "lds_ord_list": {"type": "array", 173 "items": LdsOrd.get_schema(), 174 "title": _("LDS ordinances")}, 175 "citation_list": {"type": "array", 176 "items": {"type": "string", 177 "maxLength": 50}, 178 "title": _("Citations")}, 179 "note_list": {"type": "array", 180 "items": {"type": "string", 181 "maxLength": 50}, 182 "title": _("Notes")}, 183 "change": {"type": "integer", 184 "title": _("Last changed")}, 185 "tag_list": {"type": "array", 186 "items": {"type": "string", 187 "maxLength": 50}, 188 "title": _("Tags")}, 189 "private": {"type": "boolean", 190 "title": _("Private")} 191 } 192 } 193 194 def unserialize(self, data): 195 """ 196 Convert the data held in a tuple created by the serialize method 197 back into the data in a Family structure. 198 """ 199 (self.handle, self.gramps_id, self.father_handle, self.mother_handle, 200 child_ref_list, the_type, event_ref_list, media_list, 201 attribute_list, lds_seal_list, citation_list, note_list, 202 self.change, tag_list, self.private) = data 203 204 self.type = FamilyRelType() 205 self.type.unserialize(the_type) 206 self.event_ref_list = [EventRef().unserialize(er) 207 for er in event_ref_list] 208 self.child_ref_list = [ChildRef().unserialize(cr) 209 for cr in child_ref_list] 210 MediaBase.unserialize(self, media_list) 211 AttributeBase.unserialize(self, attribute_list) 212 CitationBase.unserialize(self, citation_list) 213 NoteBase.unserialize(self, note_list) 214 LdsOrdBase.unserialize(self, lds_seal_list) 215 TagBase.unserialize(self, tag_list) 216 return self 217 218 def _has_handle_reference(self, classname, handle): 219 """ 220 Return True if the object has reference to a given handle of given 221 primary object type. 222 223 :param classname: The name of the primary object class. 224 :type classname: str 225 :param handle: The handle to be checked. 226 :type handle: str 227 :returns: Returns whether the object has reference to this handle of 228 this object type. 229 :rtype: bool 230 """ 231 if classname == 'Event': 232 return handle in [ref.ref for ref in self.event_ref_list] 233 elif classname == 'Person': 234 return handle in ([ref.ref for ref in self.child_ref_list] 235 + [self.father_handle, self.mother_handle]) 236 elif classname == 'Place': 237 return handle in [x.place for x in self.lds_ord_list] 238 return False 239 240 def _remove_handle_references(self, classname, handle_list): 241 """ 242 Remove all references in this object to object handles in the list. 243 244 :param classname: The name of the primary object class. 245 :type classname: str 246 :param handle_list: The list of handles to be removed. 247 :type handle_list: str 248 """ 249 if classname == 'Event': 250 new_list = [ref for ref in self.event_ref_list 251 if ref.ref not in handle_list] 252 self.event_ref_list = new_list 253 elif classname == 'Person': 254 new_list = [ref for ref in self.child_ref_list 255 if ref.ref not in handle_list] 256 self.child_ref_list = new_list 257 if self.father_handle in handle_list: 258 self.father_handle = None 259 if self.mother_handle in handle_list: 260 self.mother_handle = None 261 elif classname == 'Place': 262 for lds_ord in self.lds_ord_list: 263 if lds_ord.place in handle_list: 264 lds_ord.place = None 265 266 def _replace_handle_reference(self, classname, old_handle, new_handle): 267 """ 268 Replace all references to old handle with those to the new handle. 269 270 :param classname: The name of the primary object class. 271 :type classname: str 272 :param old_handle: The handle to be replaced. 273 :type old_handle: str 274 :param new_handle: The handle to replace the old one with. 275 :type new_handle: str 276 """ 277 if classname == 'Event': 278 refs_list = [ref.ref for ref in self.event_ref_list] 279 new_ref = None 280 if new_handle in refs_list: 281 new_ref = self.event_ref_list[refs_list.index(new_handle)] 282 n_replace = refs_list.count(old_handle) 283 for ix_replace in range(n_replace): 284 idx = refs_list.index(old_handle) 285 self.event_ref_list[idx].ref = new_handle 286 refs_list[idx] = new_handle 287 if new_ref: 288 evt_ref = self.event_ref_list[idx] 289 equi = new_ref.is_equivalent(evt_ref) 290 if equi != DIFFERENT: 291 if equi == EQUAL: 292 new_ref.merge(evt_ref) 293 self.event_ref_list.pop(idx) 294 refs_list.pop(idx) 295 elif classname == 'Person': 296 refs_list = [ref.ref for ref in self.child_ref_list] 297 new_ref = None 298 if new_handle in refs_list: 299 new_ref = self.child_ref_list[refs_list.index(new_handle)] 300 n_replace = refs_list.count(old_handle) 301 for ix_replace in range(n_replace): 302 idx = refs_list.index(old_handle) 303 self.child_ref_list[idx].ref = new_handle 304 refs_list[idx] = new_handle 305 if new_ref: 306 child_ref = self.child_ref_list[idx] 307 equi = new_ref.is_equivalent(child_ref) 308 if equi != DIFFERENT: 309 if equi == EQUAL: 310 new_ref.merge(child_ref) 311 self.child_ref_list.pop(idx) 312 refs_list.pop(idx) 313 if self.father_handle == old_handle: 314 self.father_handle = new_handle 315 if self.mother_handle == old_handle: 316 self.mother_handle = new_handle 317 elif classname == 'Place': 318 for lds_ord in self.lds_ord_list: 319 if lds_ord.place == old_handle: 320 lds_ord.place = new_handle 321 322 def get_text_data_list(self): 323 """ 324 Return the list of all textual attributes of the object. 325 326 :returns: Returns the list of all textual attributes of the object. 327 :rtype: list 328 """ 329 return [self.gramps_id] 330 331 def get_text_data_child_list(self): 332 """ 333 Return the list of child objects that may carry textual data. 334 335 :returns: Returns the list of child objects that may carry textual data. 336 :rtype: list 337 """ 338 add_list = [_f for _f in self.lds_ord_list if _f] 339 return self.media_list + self.attribute_list + add_list 340 341 def get_citation_child_list(self): 342 """ 343 Return the list of child secondary objects that may refer citations. 344 345 :returns: Returns the list of child secondary child objects that may 346 refer citations. 347 :rtype: list 348 """ 349 check_list = self.media_list + self.attribute_list + \ 350 self.lds_ord_list + self.child_ref_list + self.event_ref_list 351 return check_list 352 353 def get_note_child_list(self): 354 """ 355 Return the list of child secondary objects that may refer notes. 356 357 :returns: Returns the list of child secondary child objects that may 358 refer notes. 359 :rtype: list 360 """ 361 check_list = self.media_list + self.attribute_list + \ 362 self.lds_ord_list + self.child_ref_list + \ 363 self.event_ref_list 364 return check_list 365 366 def get_referenced_handles(self): 367 """ 368 Return the list of (classname, handle) tuples for all directly 369 referenced primary objects. 370 371 :returns: List of (classname, handle) tuples for referenced objects. 372 :rtype: list 373 """ 374 ret = self.get_referenced_note_handles() + \ 375 self.get_referenced_citation_handles() 376 ret += [('Person', handle) for handle 377 in ([ref.ref for ref in self.child_ref_list] + 378 [self.father_handle, self.mother_handle]) 379 if handle] 380 ret += self.get_referenced_tag_handles() 381 return ret 382 383 def get_handle_referents(self): 384 """ 385 Return the list of child objects which may, directly or through their 386 children, reference primary objects.. 387 388 :returns: Returns the list of objects referencing primary objects. 389 :rtype: list 390 """ 391 return self.media_list + self.attribute_list + \ 392 self.lds_ord_list + self.child_ref_list + self.event_ref_list 393 394 def merge(self, acquisition): 395 """ 396 Merge the content of acquisition into this family. 397 398 Lost: handle, id, relation, father, mother of acquisition. 399 400 :param acquisition: The family to merge with the present family. 401 :type acquisition: Family 402 """ 403 if self.type != acquisition.type and self.type == FamilyRelType.UNKNOWN: 404 self.set_relationship(acquisition.get_relationship()) 405 self._merge_privacy(acquisition) 406 self._merge_event_ref_list(acquisition) 407 self._merge_lds_ord_list(acquisition) 408 self._merge_media_list(acquisition) 409 self._merge_child_ref_list(acquisition) 410 self._merge_attribute_list(acquisition) 411 self._merge_note_list(acquisition) 412 self._merge_citation_list(acquisition) 413 self._merge_tag_list(acquisition) 414 415 def set_relationship(self, relationship_type): 416 """ 417 Set the relationship type between the people identified as the 418 father and mother in the relationship. 419 420 The type is a tuple whose first item is an integer constant and whose 421 second item is the string. The valid values are: 422 423 ========================= ============================================ 424 Type Description 425 ========================= ============================================ 426 FamilyRelType.MARRIED indicates a legally recognized married 427 relationship between two individuals. This 428 may be either an opposite or a same sex 429 relationship. 430 FamilyRelType.UNMARRIED indicates a relationship between two 431 individuals that is not a legally recognized 432 relationship. 433 FamilyRelType.CIVIL_UNION indicates a legally recongnized, non-married 434 relationship between two individuals of the 435 same sex. 436 FamilyRelType.UNKNOWN indicates that the type of relationship 437 between the two individuals is not know. 438 FamilyRelType.CUSTOM indicates that the type of relationship 439 between the two individuals does not match 440 any of the other types. 441 ========================= ============================================ 442 443 :param relationship_type: (int,str) tuple of the relationship type 444 between the father and mother of the relationship. 445 :type relationship_type: tuple 446 """ 447 self.type.set(relationship_type) 448 449 def get_relationship(self): 450 """ 451 Return the relationship type between the people identified as the 452 father and mother in the relationship. 453 """ 454 return self.type 455 456 def set_father_handle(self, person_handle): 457 """ 458 Set the database handle for :class:`~.person.Person` that corresponds 459 to male of the relationship. 460 461 For a same sex relationship, this can represent either of people 462 involved in the relationship. 463 464 :param person_handle: :class:`~.person.Person` database handle 465 :type person_handle: str 466 """ 467 self.father_handle = person_handle 468 469 def get_father_handle(self): 470 """ 471 Return the database handle of the :class:`~.person.Person` identified 472 as the father of the Family. 473 474 :returns: :class:`~.person.Person` database handle 475 :rtype: str 476 """ 477 return self.father_handle 478 479 def set_mother_handle(self, person_handle): 480 """ 481 Set the database handle for :class:`~.person.Person` that corresponds 482 to male of the relationship. 483 484 For a same sex relationship, this can represent either of people 485 involved in the relationship. 486 487 :param person_handle: :class:`~.person.Person` database handle 488 :type person_handle: str 489 """ 490 self.mother_handle = person_handle 491 492 def get_mother_handle(self): 493 """ 494 Return the database handle of the :class:`~.person.Person` identified 495 as the mother of the Family. 496 497 :returns: :class:`~.person.Person` database handle 498 :rtype: str 499 """ 500 return self.mother_handle 501 502 def add_child_ref(self, child_ref): 503 """ 504 Add the database handle for :class:`~.person.Person` to the Family's 505 list of children. 506 507 :param child_ref: Child Reference instance 508 :type child_ref: ChildRef 509 """ 510 if not isinstance(child_ref, ChildRef): 511 raise ValueError("expecting ChildRef instance") 512 self.child_ref_list.append(child_ref) 513 514 def remove_child_ref(self, child_ref): 515 """ 516 Remove the database handle for :class:`~.person.Person` to the Family's 517 list of children if the :class:`~.person.Person` is already in the list. 518 519 :param child_ref: Child Reference instance 520 :type child_ref: ChildRef 521 :returns: True if the handle was removed, False if it was not 522 in the list. 523 :rtype: bool 524 """ 525 if not isinstance(child_ref, ChildRef): 526 raise ValueError("expecting ChildRef instance") 527 new_list = [ref for ref in self.child_ref_list 528 if ref.ref != child_ref.ref] 529 self.child_ref_list = new_list 530 531 def remove_child_handle(self, child_handle): 532 """ 533 Remove the database handle for :class:`~.person.Person` to the Family's 534 list of children if the :class:`~.person.Person` is already in the list. 535 536 :param child_handle: :class:`~.person.Person` database handle 537 :type child_handle: str 538 :returns: True if the handle was removed, False if it was not 539 in the list. 540 :rtype: bool 541 """ 542 new_list = [ref for ref in self.child_ref_list 543 if ref.ref != child_handle] 544 self.child_ref_list = new_list 545 546 def get_child_ref_list(self): 547 """ 548 Return the list of :class:`~.childref.ChildRef` handles identifying the 549 children of the Family. 550 551 :returns: Returns the list of :class:`~.childref.ChildRef` handles 552 associated with the Family. 553 :rtype: list 554 """ 555 return self.child_ref_list 556 557 def set_child_ref_list(self, child_ref_list): 558 """ 559 Assign the passed list to the Family's list children. 560 561 :param child_ref_list: List of Child Reference instances to be 562 associated as the Family's list of children. 563 :type child_ref_list: list of :class:`~.childref.ChildRef` instances 564 """ 565 self.child_ref_list = child_ref_list 566 567 def _merge_child_ref_list(self, acquisition): 568 """ 569 Merge the list of child references from acquisition with our own. 570 571 :param acquisition: the childref list of this family will be merged 572 with the current childref list. 573 :type acquisition: Family 574 """ 575 childref_list = self.child_ref_list[:] 576 for addendum in acquisition.get_child_ref_list(): 577 for childref in childref_list: 578 equi = childref.is_equivalent(addendum) 579 if equi == IDENTICAL: 580 break 581 elif equi == EQUAL: 582 childref.merge(addendum) 583 break 584 else: 585 self.child_ref_list.append(addendum) 586 587 def add_event_ref(self, event_ref): 588 """ 589 Add the :class:`~.eventref.EventRef` to the Family instance's 590 :class:`~.eventref.EventRef` list. 591 592 This is accomplished by assigning the :class:`~.eventref.EventRef` for 593 the valid :class:`~.event.Event` in the current database. 594 595 :param event_ref: the :class:`~.eventref.EventRef` to be added to the 596 Person's :class:`~.eventref.EventRef` list. 597 :type event_ref: EventRef 598 """ 599 if event_ref and not isinstance(event_ref, EventRef): 600 raise ValueError("Expecting EventRef instance") 601 self.event_ref_list.append(event_ref) 602 603 def get_event_list(self): 604 warn("Use get_event_ref_list instead of get_event_list", 605 DeprecationWarning, 2) 606 # Wrapper for old API 607 # remove when transitition done. 608 event_handle_list = [] 609 for event_ref in self.get_event_ref_list(): 610 event_handle_list.append(event_ref.get_reference_handle()) 611 return event_handle_list 612 613 def get_event_ref_list(self): 614 """ 615 Return the list of :class:`~.eventref.EventRef` objects associated with 616 :class:`~.event.Event` instances. 617 618 :returns: Returns the list of :class:`~.eventref.EventRef` objects 619 associated with the Family instance. 620 :rtype: list 621 """ 622 return self.event_ref_list 623 624 def set_event_ref_list(self, event_ref_list): 625 """ 626 Set the Family instance's :class:`~.eventref.EventRef` list to the 627 passed list. 628 629 :param event_ref_list: List of valid :class:`~.eventref.EventRef` 630 objects 631 :type event_ref_list: list 632 """ 633 self.event_ref_list = event_ref_list 634 635 def _merge_event_ref_list(self, acquisition): 636 """ 637 Merge the list of event references from acquisition with our own. 638 639 :param acquisition: the event references list of this object will be 640 merged with the current event references list. 641 :type acquisition: Person 642 """ 643 eventref_list = self.event_ref_list[:] 644 for addendum in acquisition.get_event_ref_list(): 645 for eventref in eventref_list: 646 equi = eventref.is_equivalent(addendum) 647 if equi == IDENTICAL: 648 break 649 elif equi == EQUAL: 650 eventref.merge(addendum) 651 break 652 else: 653 self.event_ref_list.append(addendum) 654