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