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"""
25Person object for Gramps.
26"""
27
28#-------------------------------------------------------------------------
29#
30# Gramps modules
31#
32#-------------------------------------------------------------------------
33from .primaryobj import PrimaryObject
34from .citationbase import CitationBase
35from .notebase import NoteBase
36from .mediabase import MediaBase
37from .attrbase import AttributeBase
38from .addressbase import AddressBase
39from .ldsordbase import LdsOrdBase
40from .urlbase import UrlBase
41from .tagbase import TagBase
42from .name import Name
43from .eventref import EventRef
44from .personref import PersonRef
45from .attrtype import AttributeType
46from .eventroletype import EventRoleType
47from .attribute import Attribute
48from .const import IDENTICAL, EQUAL, DIFFERENT
49from ..const import GRAMPS_LOCALE as glocale
50_ = glocale.translation.gettext
51
52#-------------------------------------------------------------------------
53#
54# Person class
55#
56#-------------------------------------------------------------------------
57class Person(CitationBase, NoteBase, AttributeBase, MediaBase,
58             AddressBase, UrlBase, LdsOrdBase, PrimaryObject):
59    """
60    The Person record is the Gramps in-memory representation of an
61    individual person. It contains all the information related to
62    an individual.
63
64    Person objects are usually created in one of two ways.
65
66    1. Creating a new person object, which is then initialized and added to
67       the database.
68    2. Retrieving an object from the database using the records handle.
69
70    Once a Person object has been modified, it must be committed
71    to the database using the database object's commit_person function,
72    or the changes will be lost.
73
74    """
75
76    UNKNOWN = 2
77    MALE = 1
78    FEMALE = 0
79
80    def __init__(self, data=None):
81        """
82        Create a new Person instance.
83
84        After initialization, most data items have empty or null values,
85        including the database handle.
86        """
87        PrimaryObject.__init__(self)
88        CitationBase.__init__(self)
89        NoteBase.__init__(self)
90        MediaBase.__init__(self)
91        AttributeBase.__init__(self)
92        AddressBase.__init__(self)
93        UrlBase.__init__(self)
94        LdsOrdBase.__init__(self)
95        self.primary_name = Name()
96        self.event_ref_list = []
97        self.family_list = []
98        self.parent_family_list = []
99        self.alternate_names = []
100        self.person_ref_list = []
101        self.__gender = Person.UNKNOWN
102        self.death_ref_index = -1
103        self.birth_ref_index = -1
104        if data:
105            self.unserialize(data)
106
107        # We hold a reference to the GrampsDB so that we can maintain
108        # its genderStats.  It doesn't get set here, but from
109        # GenderStats.count_person.
110
111    def __eq__(self, other):
112        return isinstance(other, Person) and self.handle == other.handle
113
114    def __ne__(self, other):
115        return not self == other
116
117    def serialize(self):
118        """
119        Convert the data held in the Person to a Python tuple that
120        represents all the data elements.
121
122        This method is used to convert the object into a form that can easily
123        be saved to a database.
124
125        These elements may be primitive Python types (string, integers),
126        complex Python types (lists or tuples, or Python objects. If the
127        target database cannot handle complex types (such as objects or
128        lists), the database is responsible for converting the data into
129        a form that it can use.
130
131        :returns: Returns a python tuple containing the data that should
132                  be considered persistent.
133        :rtype: tuple
134        """
135        return (
136            self.handle,                                         #  0
137            self.gramps_id,                                      #  1
138            self.__gender,                                       #  2
139            self.primary_name.serialize(),                       #  3
140            [name.serialize() for name in self.alternate_names], #  4
141            self.death_ref_index,                                #  5
142            self.birth_ref_index,                                #  6
143            [er.serialize() for er in self.event_ref_list],      #  7
144            self.family_list,                                    #  8
145            self.parent_family_list,                             #  9
146            MediaBase.serialize(self),                           # 10
147            AddressBase.serialize(self),                         # 11
148            AttributeBase.serialize(self),                       # 12
149            UrlBase.serialize(self),                             # 13
150            LdsOrdBase.serialize(self),                          # 14
151            CitationBase.serialize(self),                        # 15
152            NoteBase.serialize(self),                            # 16
153            self.change,                                         # 17
154            TagBase.serialize(self),                             # 18
155            self.private,                                        # 19
156            [pr.serialize() for pr in self.person_ref_list]      # 20
157            )
158
159    @classmethod
160    def get_schema(cls):
161        """
162        Returns the JSON Schema for this class.
163
164        :returns: Returns a dict containing the schema.
165        :rtype: dict
166        """
167        from .mediaref import MediaRef
168        from .address import Address
169        from .url import Url
170        from .ldsord import LdsOrd
171        return {
172            "type": "object",
173            "title": _("Person"),
174            "properties": {
175                "_class": {"enum": [cls.__name__]},
176                "handle": {"type": "string",
177                           "maxLength": 50,
178                           "title": _("Handle")},
179                "gramps_id": {"type": "string",
180                              "title": _("Gramps ID")},
181                "gender": {"type": "integer",
182                           "minimum": 0,
183                           "maximum": 2,
184                           "title": _("Gender")},
185                "primary_name": Name.get_schema(),
186                "alternate_names": {"type": "array",
187                                    "items": Name.get_schema(),
188                                    "title": _("Alternate names")},
189                "death_ref_index": {"type": "integer",
190                                    "title": _("Death reference index")},
191                "birth_ref_index": {"type": "integer",
192                                    "title": _("Birth reference index")},
193                "event_ref_list": {"type": "array",
194                                   "items": EventRef.get_schema(),
195                                   "title": _("Event references")},
196                "family_list": {"type": "array",
197                                "items": {"type": "string",
198                                          "maxLength": 50},
199                                "title": _("Families")},
200                "parent_family_list": {"type": "array",
201                                       "items": {"type": "string",
202                                                 "maxLength": 50},
203                                       "title": _("Parent families")},
204                "media_list": {"type": "array",
205                               "items": MediaRef.get_schema(),
206                               "title": _("Media")},
207                "address_list": {"type": "array",
208                                 "items": Address.get_schema(),
209                                 "title": _("Addresses")},
210                "attribute_list": {"type": "array",
211                                   "items": Attribute.get_schema(),
212                                   "title": _("Attributes")},
213                "urls": {"type": "array",
214                         "items": Url.get_schema(),
215                         "title": _("Urls")},
216                "lds_ord_list": {"type": "array",
217                                 "items": LdsOrd.get_schema(),
218                                 "title": _("LDS ordinances")},
219                "citation_list": {"type": "array",
220                                  "items": {"type": "string",
221                                            "maxLength": 50},
222                                  "title": _("Citations")},
223                "note_list": {"type": "array",
224                              "items": {"type": "string",
225                                        "maxLength": 50},
226                              "title": _("Notes")},
227                "change": {"type": "integer",
228                           "title": _("Last changed")},
229                "tag_list": {"type": "array",
230                             "items": {"type": "string",
231                                       "maxLength": 50},
232                             "title": _("Tags")},
233                "private": {"type": "boolean",
234                            "title": _("Private")},
235                "person_ref_list": {"type": "array",
236                                    "items": PersonRef.get_schema(),
237                                    "title": _("Person references")}
238            }
239        }
240
241    def unserialize(self, data):
242        """
243        Convert the data held in a tuple created by the serialize method
244        back into the data in a Person object.
245
246        :param data: tuple containing the persistent data associated the
247                     Person object
248        :type data: tuple
249        """
250        (self.handle,             #  0
251         self.gramps_id,          #  1
252         self.__gender,           #  2
253         primary_name,            #  3
254         alternate_names,         #  4
255         self.death_ref_index,    #  5
256         self.birth_ref_index,    #  6
257         event_ref_list,          #  7
258         self.family_list,        #  8
259         self.parent_family_list, #  9
260         media_list,              # 10
261         address_list,            # 11
262         attribute_list,          # 12
263         urls,                    # 13
264         lds_ord_list,            # 14
265         citation_list,           # 15
266         note_list,               # 16
267         self.change,             # 17
268         tag_list,                # 18
269         self.private,            # 19
270         person_ref_list,         # 20
271        ) = data
272
273        self.primary_name = Name()
274        self.primary_name.unserialize(primary_name)
275        self.alternate_names = [Name().unserialize(name)
276                                for name in alternate_names]
277        self.event_ref_list = [EventRef().unserialize(er)
278                               for er in event_ref_list]
279        self.person_ref_list = [PersonRef().unserialize(pr)
280                                for pr in person_ref_list]
281        MediaBase.unserialize(self, media_list)
282        LdsOrdBase.unserialize(self, lds_ord_list)
283        AddressBase.unserialize(self, address_list)
284        AttributeBase.unserialize(self, attribute_list)
285        UrlBase.unserialize(self, urls)
286        CitationBase.unserialize(self, citation_list)
287        NoteBase.unserialize(self, note_list)
288        TagBase.unserialize(self, tag_list)
289        return self
290
291    def _has_handle_reference(self, classname, handle):
292        """
293        Return True if the object has reference to a given handle of given
294        primary object type.
295
296        :param classname: The name of the primary object class.
297        :type classname: str
298        :param handle: The handle to be checked.
299        :type handle: str
300        :returns: Returns whether the object has reference to this handle of
301                  this object type.
302        :rtype: bool
303        """
304        if classname == 'Event':
305            return any(ref.ref == handle for ref in self.event_ref_list)
306        elif classname == 'Person':
307            return any(ref.ref == handle for ref in self.person_ref_list)
308        elif classname == 'Family':
309            return any(ref == handle
310                       for ref in self.family_list + self.parent_family_list +
311                       [ordinance.famc for ordinance in self.lds_ord_list])
312        elif classname == 'Place':
313            return any(ordinance.place == handle
314                       for ordinance in self.lds_ord_list)
315        return False
316
317    def _remove_handle_references(self, classname, handle_list):
318        if classname == 'Event':
319            # Keep a copy of the birth and death references
320            birth_ref = self.get_birth_ref()
321            death_ref = self.get_death_ref()
322
323            new_list = [ref for ref in self.event_ref_list
324                        if ref.ref not in handle_list]
325            # If deleting removing the reference to the event
326            # to which birth or death ref_index points, unset the index
327            if (self.birth_ref_index != -1
328                    and self.event_ref_list[self.birth_ref_index].ref
329                    in handle_list):
330                self.set_birth_ref(None)
331            if (self.death_ref_index != -1
332                    and self.event_ref_list[self.death_ref_index].ref
333                    in handle_list):
334                self.set_death_ref(None)
335            self.event_ref_list = new_list
336
337            # Reset the indexes after deleting the event from even_ref_list
338            if self.birth_ref_index != -1:
339                self.set_birth_ref(birth_ref)
340            if self.death_ref_index != -1:
341                self.set_death_ref(death_ref)
342        elif classname == 'Person':
343            new_list = [ref for ref in self.person_ref_list
344                        if ref.ref not in handle_list]
345            self.person_ref_list = new_list
346        elif classname == 'Family':
347            new_list = [handle for handle in self.family_list
348                        if handle not in handle_list]
349            self.family_list = new_list
350            new_list = [handle for handle in self.parent_family_list
351                        if handle not in handle_list]
352            self.parent_family_list = new_list
353            for ordinance in self.lds_ord_list:
354                if ordinance.famc in handle_list:
355                    ordinance.famc = None
356        elif classname == 'Place':
357            for ordinance in self.lds_ord_list:
358                if ordinance.place in handle_list:
359                    ordinance.place = None
360
361    def _replace_handle_reference(self, classname, old_handle, new_handle):
362        if classname == 'Event':
363            refs_list = [ref.ref for ref in self.event_ref_list]
364            new_ref = None
365            if new_handle in refs_list:
366                new_ref = self.event_ref_list[refs_list.index(new_handle)]
367            n_replace = refs_list.count(old_handle)
368            for ix_replace in range(n_replace):
369                idx = refs_list.index(old_handle)
370                self.event_ref_list[idx].ref = new_handle
371                refs_list[idx] = new_handle
372                if new_ref:
373                    evt_ref = self.event_ref_list[idx]
374                    equi = new_ref.is_equivalent(evt_ref)
375                    if equi != DIFFERENT:
376                        if equi == EQUAL:
377                            new_ref.merge(evt_ref)
378                        self.event_ref_list.pop(idx)
379                        refs_list.pop(idx)
380                        if idx < self.birth_ref_index:
381                            self.birth_ref_index -= 1
382                        elif idx == self.birth_ref_index:
383                            self.birth_ref_index = -1
384                            # birth_ref_index should be recalculated which
385                            # needs database access!
386                        if idx < self.death_ref_index:
387                            self.death_ref_index -= 1
388                        elif idx == self.death_ref_index:
389                            self.death_ref_index = -1
390                            # death_ref_index should be recalculated which
391                            # needs database access!
392        elif classname == 'Person':
393            refs_list = [ref.ref for ref in self.person_ref_list]
394            new_ref = None
395            if new_handle in refs_list:
396                new_ref = self.person_ref_list[refs_list.index(new_handle)]
397            n_replace = refs_list.count(old_handle)
398            for ix_replace in range(n_replace):
399                idx = refs_list.index(old_handle)
400                self.person_ref_list[idx].ref = new_handle
401                refs_list[idx] = new_handle
402                if new_ref:
403                    person_ref = self.person_ref_list[idx]
404                    equi = new_ref.is_equivalent(person_ref)
405                    if equi != DIFFERENT:
406                        if equi == EQUAL:
407                            new_ref.merge(person_ref)
408                        self.person_ref_list.pop(idx)
409                        refs_list.pop(idx)
410        elif classname == 'Family':
411            while old_handle in self.family_list:
412                ix = self.family_list.index(old_handle)
413                self.family_list[ix] = new_handle
414            while old_handle in self.parent_family_list:
415                ix = self.parent_family_list.index(old_handle)
416                self.parent_family_list[ix] = new_handle
417            handle_list = [ordinance.famc for ordinance in self.lds_ord_list]
418            while old_handle in handle_list:
419                ix = handle_list.index(old_handle)
420                self.lds_ord_list[ix].famc = new_handle
421                handle_list[ix] = ''
422        elif classname == "Place":
423            handle_list = [ordinance.place for ordinance in self.lds_ord_list]
424            while old_handle in handle_list:
425                ix = handle_list.index(old_handle)
426                self.lds_ord_list[ix].place = new_handle
427                handle_list[ix] = ''
428
429    def get_text_data_list(self):
430        """
431        Return the list of all textual attributes of the object.
432
433        :returns: Returns the list of all textual attributes of the object.
434        :rtype: list
435        """
436        return [self.gramps_id]
437
438    def get_text_data_child_list(self):
439        """
440        Return the list of child objects that may carry textual data.
441
442        :returns: Returns the list of child objects that may carry textual data.
443        :rtype: list
444        """
445        check_list = self.lds_ord_list
446        add_list = [_f for _f in check_list if _f]
447        return ([self.primary_name] +
448                self.media_list +
449                self.alternate_names +
450                self.address_list +
451                self.attribute_list +
452                self.urls +
453                self.event_ref_list +
454                add_list +
455                self.person_ref_list
456               )
457
458    def get_citation_child_list(self):
459        """
460        Return the list of child secondary objects that may refer citations.
461
462        :returns: Returns the list of child secondary child objects that may
463                  refer citations.
464        :rtype: list
465        """
466        return ([self.primary_name] +
467                self.media_list +
468                self.alternate_names +
469                self.address_list +
470                self.attribute_list +
471                self.lds_ord_list +
472                self.person_ref_list +
473                self.event_ref_list
474               )
475
476    def get_note_child_list(self):
477        """
478        Return the list of child secondary objects that may refer notes.
479
480        :returns: Returns the list of child secondary child objects that may
481                  refer notes.
482        :rtype: list
483        """
484        return ([self.primary_name] +
485                self.media_list +
486                self.alternate_names +
487                self.address_list +
488                self.attribute_list +
489                self.lds_ord_list +
490                self.person_ref_list +
491                self.event_ref_list
492               )
493
494    def get_referenced_handles(self):
495        """
496        Return the list of (classname, handle) tuples for all directly
497        referenced primary objects.
498
499        :returns: List of (classname, handle) tuples for referenced objects.
500        :rtype: list
501        """
502        return [('Family', handle) for handle in
503                (self.family_list + self.parent_family_list)] + (
504                    self.get_referenced_note_handles() +
505                    self.get_referenced_citation_handles() +
506                    self.get_referenced_tag_handles()
507                )
508
509    def get_handle_referents(self):
510        """
511        Return the list of child objects which may, directly or through
512        their children, reference primary objects.
513
514        :returns: Returns the list of objects referencing primary objects.
515        :rtype: list
516        """
517        return ([self.primary_name] +
518                self.media_list +
519                self.alternate_names +
520                self.address_list +
521                self.attribute_list +
522                self.lds_ord_list +
523                self.person_ref_list +
524                self.event_ref_list
525               )
526
527    def merge(self, acquisition):
528        """
529        Merge the content of acquisition into this person.
530
531        :param acquisition: The person to merge with the present person.
532        :type acquisition: Person
533        """
534        acquisition_id = acquisition.get_gramps_id()
535        if acquisition_id:
536            attr = Attribute()
537            attr.set_type(_("Merged Gramps ID"))
538            attr.set_value(acquisition.get_gramps_id())
539            self.add_attribute(attr)
540
541        self._merge_privacy(acquisition)
542        acquisition.alternate_names.insert(0, acquisition.get_primary_name())
543        self._merge_alternate_names(acquisition)
544        self._merge_event_ref_list(acquisition)
545        self._merge_lds_ord_list(acquisition)
546        self._merge_media_list(acquisition)
547        self._merge_address_list(acquisition)
548        self._merge_attribute_list(acquisition)
549        self._merge_url_list(acquisition)
550        self._merge_person_ref_list(acquisition)
551        self._merge_note_list(acquisition)
552        self._merge_citation_list(acquisition)
553        self._merge_tag_list(acquisition)
554
555        list(map(self.add_parent_family_handle,
556                 acquisition.get_parent_family_handle_list()))
557        list(map(self.add_family_handle, acquisition.get_family_handle_list()))
558
559    def set_primary_name(self, name):
560        """
561        Set the primary name of the Person to the specified :class:`~.name.Name`
562        instance.
563
564        :param name: :class:`~.name.Name` to be assigned to the person
565        :type name: :class:`~.name.Name`
566        """
567        self.primary_name = name
568
569    def get_primary_name(self):
570        """
571        Return the :class:`~.name.Name` instance marked as the Person's primary
572        name.
573
574        :returns: Returns the primary name
575        :rtype: :class:`~.name.Name`
576        """
577        return self.primary_name
578
579    def get_alternate_names(self):
580        """
581        Return the list of alternate :class:`~.name.Name` instances.
582
583        :returns: List of :class:`~.name.Name` instances
584        :rtype: list
585        """
586        return self.alternate_names
587
588    def set_alternate_names(self, alt_name_list):
589        """
590        Change the list of alternate names to the passed list.
591
592        :param alt_name_list: List of :class:`~.name.Name` instances
593        :type alt_name_list: list
594        """
595        self.alternate_names = alt_name_list
596
597    def _merge_alternate_names(self, acquisition):
598        """
599        Merge the list of alternate names from acquisition with our own.
600
601        :param acquisition: the list of alternate names of this object will be
602                            merged with the current alternate name list.
603        :rtype acquisition: Person
604        """
605        name_list = self.alternate_names[:]
606        primary_name = self.get_primary_name()
607        if primary_name and not primary_name.is_empty():
608            name_list.insert(0, primary_name)
609        for addendum in acquisition.get_alternate_names():
610            for name in name_list:
611                equi = name.is_equivalent(addendum)
612                if equi == IDENTICAL:
613                    break
614                elif equi == EQUAL:
615                    name.merge(addendum)
616                    break
617            else:
618                self.alternate_names.append(addendum)
619
620    def add_alternate_name(self, name):
621        """
622        Add a :class:`~.name.Name` instance to the list of alternative names.
623
624        :param name: :class:`~.name.Name` to add to the list
625        :type name: :class:`~.name.Name`
626        """
627        self.alternate_names.append(name)
628
629    def get_nick_name(self):
630        for name in [self.get_primary_name()] + self.get_alternate_names():
631            if name.get_nick_name():
632                return name.get_nick_name()
633        for attr in self.attribute_list:
634            if int(attr.type) == AttributeType.NICKNAME:
635                return attr.get_value()
636        return ''
637
638    def set_gender(self, gender):
639        """
640        Set the gender of the Person.
641
642        :param gender: Assigns the Person's gender to one of the
643                       following constants:
644
645                       - Person.MALE
646                       - Person.FEMALE
647                       - Person.UNKNOWN
648        :type gender: int
649        """
650        if gender not in (Person.MALE, Person.FEMALE, Person.UNKNOWN):
651            raise ValueError('Attempt to assign invalid gender')
652        self.__gender = gender
653
654    def get_gender(self):
655        """
656        Return the gender of the Person.
657
658        :returns: Returns one of the following constants:
659
660                  - Person.MALE
661                  - Person.FEMALE
662                  - Person.UNKNOWN
663        :rtype: int
664        """
665        return self.__gender
666
667    gender = property(get_gender, set_gender, None,
668                      'Returns or sets the gender of the person')
669
670    def set_birth_ref(self, event_ref):
671        """
672        Assign the birth event to the Person object.
673
674        This is accomplished by assigning the :class:`~.eventref.EventRef` of
675        the birth event in the current database.
676
677        :param event_ref: the :class:`~.eventref.EventRef` object associated
678                          with the Person's birth.
679        :type event_ref: EventRef
680        """
681        if event_ref and not isinstance(event_ref, EventRef):
682            raise ValueError("Expecting EventRef instance")
683        if event_ref is None:
684            self.birth_ref_index = -1
685            return
686
687        # check whether we already have this ref in the list
688        for self.birth_ref_index, ref in enumerate(self.event_ref_list):
689            if event_ref.is_equal(ref):
690                return    # Note: self.birth_ref_index already set
691
692        self.event_ref_list.append(event_ref)
693        self.birth_ref_index = len(self.event_ref_list)-1
694
695    def set_death_ref(self, event_ref):
696        """
697        Assign the death event to the Person object.
698
699        This is accomplished by assigning the :class:`~.eventref.EventRef` of
700        the death event in the current database.
701
702        :param event_ref: the :class:`~.eventref.EventRef` object associated
703                          with the Person's death.
704        :type event_ref: EventRef
705        """
706        if event_ref and not isinstance(event_ref, EventRef):
707            raise ValueError("Expecting EventRef instance")
708        if event_ref is None:
709            self.death_ref_index = -1
710            return
711
712        # check whether we already have this ref in the list
713        for self.death_ref_index, ref in enumerate(self.event_ref_list):
714            if event_ref.is_equal(ref):
715                return    # Note: self.death_ref_index already set
716
717        self.event_ref_list.append(event_ref)
718        self.death_ref_index = len(self.event_ref_list)-1
719
720    def get_birth_ref(self):
721        """
722        Return the :class:`~.eventref.EventRef` for Person's birth event.
723
724        This should correspond to an :class:`~.event.Event` in the database's
725        :class:`~.event.Event` list.
726
727        :returns: Returns the birth :class:`~.eventref.EventRef` or None if no
728                  birth :class:`~.event.Event` has been assigned.
729        :rtype: EventRef
730        """
731
732        if 0 <= self.birth_ref_index < len(self.event_ref_list):
733            return self.event_ref_list[self.birth_ref_index]
734        else:
735            return None
736
737    def get_death_ref(self):
738        """
739        Return the :class:`~.eventref.EventRef` for the Person's death event.
740
741        This should correspond to an :class:`~.event.Event` in the database's
742        :class:`~.event.Event` list.
743
744        :returns: Returns the death :class:`~.eventref.EventRef` or None if no
745                  death :class:`~.event.Event` has been assigned.
746        :rtype: event_ref
747        """
748
749        if 0 <= self.death_ref_index < len(self.event_ref_list):
750            return self.event_ref_list[self.death_ref_index]
751        else:
752            return None
753
754    def add_event_ref(self, event_ref):
755        """
756        Add the :class:`~.eventref.EventRef` to the Person instance's
757        :class:`~.eventref.EventRef` list.
758
759        This is accomplished by assigning the :class:`~.eventref.EventRef` of a
760        valid :class:`~.event.Event` in the current database.
761
762        :param event_ref: the :class:`~.eventref.EventRef` to be added to the
763                          Person's :class:`~.eventref.EventRef` list.
764        :type event_ref: EventRef
765        """
766        if event_ref and not isinstance(event_ref, EventRef):
767            raise ValueError("Expecting EventRef instance")
768
769        # check whether we already have this ref in the list
770        if not any(event_ref.is_equal(ref) for ref in self.event_ref_list):
771            self.event_ref_list.append(event_ref)
772
773    def get_event_ref_list(self):
774        """
775        Return the list of :class:`~.eventref.EventRef` objects associated with
776        :class:`~.event.Event` instances.
777
778        :returns: Returns the list of :class:`~.eventref.EventRef` objects
779                  associated with the Person instance.
780        :rtype: list
781        """
782        return self.event_ref_list
783
784    def get_primary_event_ref_list(self):
785        """
786        Return the list of :class:`~.eventref.EventRef` objects associated with
787        :class:`~.event.Event` instances that have been marked as primary
788        events.
789
790        :returns: Returns generator of :class:`~.eventref.EventRef` objects
791                  associated with the Person instance.
792        :rtype: generator
793        """
794        return (ref for ref in self.event_ref_list
795                if ref.get_role() == EventRoleType.PRIMARY
796               )
797
798    def set_event_ref_list(self, event_ref_list):
799        """
800        Set the Person instance's :class:`~.eventref.EventRef` list to the
801        passed list.
802
803        :param event_ref_list: List of valid :class:`~.eventref.EventRef`
804                               objects.
805        :type event_ref_list: list
806        """
807        self.event_ref_list = event_ref_list
808
809    def _merge_event_ref_list(self, acquisition):
810        """
811        Merge the list of event references from acquisition with our own.
812
813        :param acquisition: the event references list of this object will be
814                            merged with the current event references list.
815        :rtype acquisition: Person
816        """
817        eventref_list = self.event_ref_list[:]
818        for idx, addendum in enumerate(acquisition.get_event_ref_list()):
819            for eventref in eventref_list:
820                equi = eventref.is_equivalent(addendum)
821                if equi == IDENTICAL:
822                    break
823                elif equi == EQUAL:
824                    eventref.merge(addendum)
825                    break
826            else:
827                self.event_ref_list.append(addendum)
828                if (self.birth_ref_index == -1 and
829                        idx == acquisition.birth_ref_index):
830                    self.birth_ref_index = len(self.event_ref_list) - 1
831                if (self.death_ref_index == -1 and
832                        idx == acquisition.death_ref_index):
833                    self.death_ref_index = len(self.event_ref_list) - 1
834
835    def add_family_handle(self, family_handle):
836        """
837        Add the :class:`~.family.Family` handle to the Person instance's
838        :class:`~.family.Family` list.
839
840        This is accomplished by assigning the handle of a valid
841        :class:`~.family.Family` in the current database.
842
843        Adding a :class:`~.family.Family` handle to a Person does not
844        automatically update the corresponding :class:`~.family.Family`. The
845        developer is responsible to make sure that when a
846        :class:`~.family.Family` is added to Person, that the Person is assigned
847        to either the father or mother role in the :class:`~.family.Family`.
848
849        :param family_handle: handle of the :class:`~.family.Family` to be added
850                              to the Person's :class:`~.family.Family` list.
851        :type family_handle: str
852        """
853        if family_handle not in self.family_list:
854            self.family_list.append(family_handle)
855
856    def set_preferred_family_handle(self, family_handle):
857        """
858        Set the family_handle specified to be the preferred
859        :class:`~.family.Family`.
860
861        The preferred :class:`~.family.Family` is determined by the first
862        :class:`~.family.Family` in the :class:`~.family.Family` list, and is
863        typically used to indicate the preferred :class:`~.family.Family` for
864        navigation or reporting.
865
866        The family_handle must already be in the list, or the function
867        call has no effect.
868
869        :param family_handle: Handle of the :class:`~.family.Family` to make the
870                              preferred :class:`~.family.Family`.
871        :type family_handle: str
872        :returns: True if the call succeeded, False if the family_handle
873                  was not already in the :class:`~.family.Family` list.
874        :rtype: bool
875        """
876        if family_handle in self.family_list:
877            self.family_list.remove(family_handle)
878            self.family_list = [family_handle] + self.family_list
879            return True
880        else:
881            return False
882
883    def get_family_handle_list(self):
884        """
885        Return the list of :class:`~.family.Family` handles in which the person
886        is a parent or spouse.
887
888        :returns: Returns the list of handles corresponding to the
889                  :class:`~.family.Family` records with which the person
890                  is associated.
891        :rtype: list
892        """
893        return self.family_list
894
895    def set_family_handle_list(self, family_list):
896        """
897        Assign the passed list to the Person's list of families in which it is
898        a parent or spouse.
899
900        :param family_list: List of :class:`~.family.Family` handles to be
901                            associated with the Person
902        :type family_list: list
903        """
904        self.family_list = family_list
905
906    def clear_family_handle_list(self):
907        """
908        Remove all :class:`~.family.Family` handles from the
909        :class:`~.family.Family` list.
910        """
911        self.family_list = []
912
913    def remove_family_handle(self, family_handle):
914        """
915        Remove the specified :class:`~.family.Family` handle from the list of
916        marriages/partnerships.
917
918        If the handle does not exist in the list, the operation has no effect.
919
920        :param family_handle: :class:`~.family.Family` handle to remove from
921                              the list
922        :type family_handle: str
923
924        :returns: True if the handle was removed, False if it was not
925                  in the list.
926        :rtype: bool
927        """
928        if family_handle in self.family_list:
929            self.family_list.remove(family_handle)
930            return True
931        else:
932            return False
933
934    def get_parent_family_handle_list(self):
935        """
936        Return the list of :class:`~.family.Family` handles in which the person
937        is a child.
938
939        :returns: Returns the list of handles corresponding to the
940                  :class:`~.family.Family` records with which the person is a
941                  child.
942        :rtype: list
943        """
944        return self.parent_family_list
945
946    def set_parent_family_handle_list(self, family_list):
947        """
948        Return the list of :class:`~.family.Family` handles in which the person
949        is a child.
950
951        :returns: Returns the list of handles corresponding to the
952                  :class:`~.family.Family` records with which the person is a
953                  child.
954        :rtype: list
955        """
956        self.parent_family_list = family_list
957
958    def add_parent_family_handle(self, family_handle):
959        """
960        Add the :class:`~.family.Family` handle to the Person instance's list of
961        families in which it is a child.
962
963        This is accomplished by assigning the handle of a valid
964        :class:`~.family.Family` in the current database.
965
966        Adding a :class:`~.family.Family` handle to a Person does not
967        automatically update the corresponding :class:`~.family.Family`. The
968        developer is responsible to make sure that when a
969        :class:`~.family.Family` is added to Person, that the Person is
970        added to the :class:`~.family.Family` instance's child list.
971
972        :param family_handle: handle of the :class:`~.family.Family` to be added
973                              to the Person's :class:`~.family.Family` list.
974        :type family_handle: str
975        """
976        if not isinstance(family_handle, str):
977            raise ValueError("Expecting handle, obtained %s" % str(family_handle))
978        if family_handle not in self.parent_family_list:
979            self.parent_family_list.append(family_handle)
980
981    def clear_parent_family_handle_list(self):
982        """
983        Remove all :class:`~.family.Family` handles from the parent
984        :class:`~.family.Family` list.
985        """
986        self.parent_family_list = []
987
988    def remove_parent_family_handle(self, family_handle):
989        """
990        Remove the specified :class:`~.family.Family` handle from the list of
991        parent families (families in which the parent is a child).
992
993        If the handle does not exist in the list, the operation has no effect.
994
995        :param family_handle: :class:`~.family.Family` handle to remove from the
996                              list
997        :type family_handle: str
998
999        :returns: Returns a tuple of three strings, consisting of the
1000                  removed handle, relationship to mother, and relationship
1001                  to father. None is returned if the handle is not in the
1002                  list.
1003        :rtype: tuple
1004        """
1005        if family_handle in self.parent_family_list:
1006            self.parent_family_list.remove(family_handle)
1007            return True
1008        else:
1009            return False
1010
1011    def set_main_parent_family_handle(self, family_handle):
1012        """
1013        Set the main :class:`~.family.Family` in which the Person is a child.
1014
1015        The main :class:`~.family.Family` is the :class:`~.family.Family`
1016        typically used for reports and navigation. This is accomplished by
1017        moving the :class:`~.family.Family` to the beginning of the list. The
1018        family_handle must be in the list for this to have any effect.
1019
1020        :param family_handle: handle of the :class:`~.family.Family` to be
1021                              marked as the main :class:`~.family.Family`
1022        :type family_handle: str
1023        :returns: Returns True if the assignment has successful
1024        :rtype: bool
1025        """
1026        if family_handle in self.parent_family_list:
1027            self.parent_family_list.remove(family_handle)
1028            self.parent_family_list = [family_handle] + self.parent_family_list
1029            return True
1030        else:
1031            return False
1032
1033    def get_main_parents_family_handle(self):
1034        """
1035        Return the handle of the :class:`~.family.Family` considered to be the
1036        main :class:`~.family.Family` in which the Person is a child.
1037
1038        :returns: Returns the family_handle if a family_handle exists,
1039                  If no :class:`~.family.Family` is assigned, None is returned
1040        :rtype: str
1041        """
1042        if self.parent_family_list:
1043            return self.parent_family_list[0]
1044        else:
1045            return None
1046
1047    def add_person_ref(self, person_ref):
1048        """
1049        Add the :class:`~.personref.PersonRef` to the Person instance's
1050        :class:`~.personref.PersonRef` list.
1051
1052        :param person_ref: the :class:`~.personref.PersonRef` to be added to the
1053                           Person's :class:`~.personref.PersonRef` list.
1054        :type person_ref: PersonRef
1055        """
1056        if person_ref and not isinstance(person_ref, PersonRef):
1057            raise ValueError("Expecting PersonRef instance")
1058        self.person_ref_list.append(person_ref)
1059
1060    def get_person_ref_list(self):
1061        """
1062        Return the list of :class:`~.personref.PersonRef` objects.
1063
1064        :returns: Returns the list of :class:`~.personref.PersonRef` objects.
1065        :rtype: list
1066        """
1067        return self.person_ref_list
1068
1069    def set_person_ref_list(self, person_ref_list):
1070        """
1071        Set the Person instance's :class:`~.personref.PersonRef` list to the
1072        passed list.
1073
1074        :param person_ref_list: List of valid :class:`~.personref.PersonRef`
1075                                objects
1076        :type person_ref_list: list
1077        """
1078        self.person_ref_list = person_ref_list
1079
1080    def _merge_person_ref_list(self, acquisition):
1081        """
1082        Merge the list of person references from acquisition with our own.
1083
1084        :param acquisition: the list of person references of this person will be
1085                            merged with the current person references list.
1086        :rtype acquisition: Person
1087        """
1088        personref_list = self.person_ref_list[:]
1089        for addendum in acquisition.get_person_ref_list():
1090            for personref in personref_list:
1091                equi = personref.is_equivalent(addendum)
1092                if equi == IDENTICAL:
1093                    break
1094                elif equi == EQUAL:
1095                    personref.merge(addendum)
1096                    break
1097            else:
1098                self.person_ref_list.append(addendum)
1099