1# 2# Gramps - a GTK+/GNOME based genealogy program 3# 4# Copyright (C) 2000-2007 Donald N. Allingham 5# Copyright (C) 2008 Brian G. Matherly 6# Copyright (C) 2010 Jakim Friant 7# Copyright (C) 2011 Paul Franklin 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""" 25A plugin to verify the data against user-adjusted tests. 26This is the research tool, not the low-level data ingerity check. 27 28Note that this tool has an old heritage (20-Oct-2002 at least) and 29so there are vestages of earlier ways of doing things which have not 30been converted to a more-modern way. For instance the way the tool 31options are defined (and read in) is not done the way it would be now. 32""" 33 34# pylint: disable=not-callable 35# pylint: disable=no-self-use 36# pylint: disable=undefined-variable 37 38#------------------------------------------------------------------------ 39# 40# standard python modules 41# 42#------------------------------------------------------------------------ 43 44import os 45import pickle 46from hashlib import md5 47 48#------------------------------------------------------------------------ 49# 50# GNOME/GTK modules 51# 52#------------------------------------------------------------------------ 53from gi.repository import Gdk 54from gi.repository import Gtk 55from gi.repository import GObject 56 57#------------------------------------------------------------------------ 58# 59# Gramps modules 60# 61#------------------------------------------------------------------------ 62from gramps.gen.const import GRAMPS_LOCALE as glocale 63_ = glocale.translation.sgettext 64from gramps.gen.errors import WindowActiveError 65from gramps.gen.const import URL_MANUAL_PAGE, VERSION_DIR 66from gramps.gen.lib import (ChildRefType, EventRoleType, EventType, 67 FamilyRelType, NameType, Person) 68from gramps.gen.lib.date import Today 69from gramps.gui.editors import EditPerson, EditFamily 70from gramps.gen.utils.db import family_name 71from gramps.gui.display import display_help 72from gramps.gui.managedwindow import ManagedWindow 73from gramps.gen.updatecallback import UpdateCallback 74from gramps.gui.plug import tool 75from gramps.gui.glade import Glade 76 77#------------------------------------------------------------------------- 78# 79# Constants 80# 81#------------------------------------------------------------------------- 82WIKI_HELP_PAGE = '%s_-_Tools' % URL_MANUAL_PAGE 83WIKI_HELP_SEC = _('manual|Verify_the_Data') 84 85#------------------------------------------------------------------------- 86# 87# temp storage and related functions 88# 89#------------------------------------------------------------------------- 90_person_cache = {} 91_family_cache = {} 92_event_cache = {} 93_today = Today().get_sort_value() 94 95def find_event(db, handle): 96 """ find an event, given a handle """ 97 if handle in _event_cache: 98 obj = _event_cache[handle] 99 else: 100 obj = db.get_event_from_handle(handle) 101 _event_cache[handle] = obj 102 return obj 103 104def find_person(db, handle): 105 """ find a person, given a handle """ 106 if handle in _person_cache: 107 obj = _person_cache[handle] 108 else: 109 obj = db.get_person_from_handle(handle) 110 _person_cache[handle] = obj 111 return obj 112 113def find_family(db, handle): 114 """ find a family, given a handle """ 115 if handle in _family_cache: 116 obj = _family_cache[handle] 117 else: 118 obj = db.get_family_from_handle(handle) 119 _family_cache[handle] = obj 120 return obj 121 122def clear_cache(): 123 """ clear the cache """ 124 _person_cache.clear() 125 _family_cache.clear() 126 _event_cache.clear() 127 128#------------------------------------------------------------------------- 129# 130# helper functions 131# 132#------------------------------------------------------------------------- 133def get_date_from_event_handle(db, event_handle, estimate=False): 134 """ get a date from an event handle """ 135 if not event_handle: 136 return 0 137 event = find_event(db, event_handle) 138 if event: 139 date_obj = event.get_date_object() 140 if (not estimate 141 and (date_obj.get_day() == 0 or date_obj.get_month() == 0)): 142 return 0 143 return date_obj.get_sort_value() 144 else: 145 return 0 146 147def get_date_from_event_type(db, person, event_type, estimate=False): 148 """ get a date from a person's specific event type """ 149 if not person: 150 return 0 151 for event_ref in person.get_event_ref_list(): 152 event = find_event(db, event_ref.ref) 153 if event: 154 if (event_ref.get_role() != EventRoleType.PRIMARY 155 and event.get_type() == EventType.BURIAL): 156 continue 157 if event.get_type() == event_type: 158 date_obj = event.get_date_object() 159 if (not estimate 160 and (date_obj.get_day() == 0 161 or date_obj.get_month() == 0)): 162 return 0 163 return date_obj.get_sort_value() 164 return 0 165 166def get_bapt_date(db, person, estimate=False): 167 """ get a person's baptism date """ 168 return get_date_from_event_type(db, person, 169 EventType.BAPTISM, estimate) 170 171def get_bury_date(db, person, estimate=False): 172 """ get a person's burial date """ 173 # check role on burial event 174 for event_ref in person.get_event_ref_list(): 175 event = find_event(db, event_ref.ref) 176 if (event 177 and event.get_type() == EventType.BURIAL 178 and event_ref.get_role() == EventRoleType.PRIMARY): 179 return get_date_from_event_type(db, person, 180 EventType.BURIAL, estimate) 181 182def get_birth_date(db, person, estimate=False): 183 """ get a person's birth date (or baptism date if 'estimated') """ 184 if not person: 185 return 0 186 birth_ref = person.get_birth_ref() 187 if not birth_ref: 188 ret = 0 189 else: 190 ret = get_date_from_event_handle(db, birth_ref.ref, estimate) 191 if estimate and (ret == 0): 192 ret = get_bapt_date(db, person, estimate) 193 ret = 0 if ret is None else ret 194 return ret 195 196def get_death(db, person): 197 """ 198 boolean whether there is a death event or not 199 (if a user claims a person is dead, we will believe it even with no date) 200 """ 201 if not person: 202 return False 203 death_ref = person.get_death_ref() 204 if death_ref: 205 return True 206 else: 207 return False 208 209def get_death_date(db, person, estimate=False): 210 """ get a person's death date (or burial date if 'estimated') """ 211 if not person: 212 return 0 213 death_ref = person.get_death_ref() 214 if not death_ref: 215 ret = 0 216 else: 217 ret = get_date_from_event_handle(db, death_ref.ref, estimate) 218 if estimate and (ret == 0): 219 ret = get_bury_date(db, person, estimate) 220 ret = 0 if ret is None else ret 221 return ret 222 223def get_age_at_death(db, person, estimate): 224 """ get a person's age at death """ 225 birth_date = get_birth_date(db, person, estimate) 226 death_date = get_death_date(db, person, estimate) 227 if (birth_date > 0) and (death_date > 0): 228 return death_date - birth_date 229 return 0 230 231def get_father(db, family): 232 """ get a family's father """ 233 if not family: 234 return None 235 father_handle = family.get_father_handle() 236 if father_handle: 237 return find_person(db, father_handle) 238 return None 239 240def get_mother(db, family): 241 """ get a family's mother """ 242 if not family: 243 return None 244 mother_handle = family.get_mother_handle() 245 if mother_handle: 246 return find_person(db, mother_handle) 247 return None 248 249def get_child_birth_dates(db, family, estimate): 250 """ get a family's children's birth dates """ 251 dates = [] 252 for child_ref in family.get_child_ref_list(): 253 child = find_person(db, child_ref.ref) 254 child_birth_date = get_birth_date(db, child, estimate) 255 if child_birth_date > 0: 256 dates.append(child_birth_date) 257 return dates 258 259def get_n_children(db, person): 260 """ get the number of a family's children """ 261 number = 0 262 for family_handle in person.get_family_handle_list(): 263 family = find_family(db, family_handle) 264 if family: 265 number += len(family.get_child_ref_list()) 266 return number 267 268def get_marriage_date(db, family): 269 """ get a family's marriage date """ 270 if not family: 271 return 0 272 for event_ref in family.get_event_ref_list(): 273 event = find_event(db, event_ref.ref) 274 if (event.get_type() == EventType.MARRIAGE 275 and (event_ref.get_role() == EventRoleType.FAMILY 276 or event_ref.get_role() == EventRoleType.PRIMARY)): 277 date_obj = event.get_date_object() 278 return date_obj.get_sort_value() 279 return 0 280 281#------------------------------------------------------------------------- 282# 283# Actual tool 284# 285#------------------------------------------------------------------------- 286class Verify(tool.Tool, ManagedWindow, UpdateCallback): 287 """ 288 A plugin to verify the data against user-adjusted tests. 289 This is the research tool, not the low-level data ingerity check. 290 """ 291 292 def __init__(self, dbstate, user, options_class, name, callback=None): 293 """ initialize things """ 294 uistate = user.uistate 295 self.label = _('Data Verify tool') 296 self.v_r = None 297 tool.Tool.__init__(self, dbstate, options_class, name) 298 ManagedWindow.__init__(self, uistate, [], self.__class__) 299 if uistate: 300 UpdateCallback.__init__(self, self.uistate.pulse_progressbar) 301 302 self.dbstate = dbstate 303 if uistate: 304 self.init_gui() 305 else: 306 self.add_results = self.add_results_cli 307 self.run_the_tool(cli=True) 308 309 def add_results_cli(self, results): 310 """ print data for the user, no GUI """ 311 (msg, gramps_id, name, the_type, rule_id, severity, handle) = results 312 severity_str = 'S' 313 if severity == Rule.WARNING: 314 severity_str = 'W' 315 elif severity == Rule.ERROR: 316 severity_str = 'E' 317 # translators: needed for French+Arabic, ignore otherwise 318 print(_("%(severity)s: %(msg)s, %(type)s: %(gid)s, %(name)s" 319 ) % {'severity' : severity_str, 'msg' : msg, 'type' : the_type, 320 'gid' : gramps_id, 'name' : name}) 321 322 def init_gui(self): 323 """ Draw dialog and make it handle everything """ 324 self.v_r = None 325 self.top = Glade() 326 self.top.connect_signals({ 327 "destroy_passed_object" : self.close, 328 "on_help_clicked" : self.on_help_clicked, 329 "on_verify_ok_clicked" : self.on_apply_clicked, 330 "on_delete_event" : self.close, 331 }) 332 333 window = self.top.toplevel 334 self.set_window(window, self.top.get_object('title'), self.label) 335 self.setup_configs('interface.verify', 650, 400) 336 337 o_dict = self.options.handler.options_dict 338 for option in o_dict: 339 if option in ['estimate_age', 'invdate']: 340 self.top.get_object(option).set_active(o_dict[option]) 341 else: 342 self.top.get_object(option).set_value(o_dict[option]) 343 self.show() 344 345 def build_menu_names(self, obj): 346 """ build the menu names """ 347 return (_("Tool settings"), self.label) 348 349 def on_help_clicked(self, obj): 350 """ Display the relevant portion of Gramps manual """ 351 display_help(webpage=WIKI_HELP_PAGE, section=WIKI_HELP_SEC) 352 353 def on_apply_clicked(self, obj): 354 """ event handler for user clicking the OK button: start things """ 355 run_button = self.top.get_object('button4') 356 close_button = self.top.get_object('button5') 357 run_button.set_sensitive(False) 358 close_button.set_sensitive(False) 359 o_dict = self.options.handler.options_dict 360 for option in o_dict: 361 if option in ['estimate_age', 'invdate']: 362 o_dict[option] = self.top.get_object(option).get_active() 363 else: 364 o_dict[option] = self.top.get_object(option).get_value_as_int() 365 366 try: 367 self.v_r = VerifyResults(self.dbstate, self.uistate, self.track, 368 self.top, self.close) 369 self.add_results = self.v_r.add_results 370 self.v_r.load_ignored(self.db.full_name) 371 except WindowActiveError: 372 pass 373 except AttributeError: # VerifyResults.load_ignored was not run 374 self.v_r.ignores = {} 375 376 self.uistate.set_busy_cursor(True) 377 self.uistate.progress.show() 378 busy_cursor = Gdk.Cursor.new_for_display(Gdk.Display.get_default(), 379 Gdk.CursorType.WATCH) 380 self.window.get_window().set_cursor(busy_cursor) 381 try: 382 self.v_r.window.get_window().set_cursor(busy_cursor) 383 except AttributeError: 384 pass 385 386 self.run_the_tool(cli=False) 387 388 self.uistate.progress.hide() 389 self.uistate.set_busy_cursor(False) 390 try: 391 self.window.get_window().set_cursor(None) 392 self.v_r.window.get_window().set_cursor(None) 393 except AttributeError: 394 pass 395 run_button.set_sensitive(True) 396 close_button.set_sensitive(True) 397 self.reset() 398 399 # Save options 400 self.options.handler.save_options() 401 402 def run_the_tool(self, cli=False): 403 """ run the tool """ 404 405 person_handles = self.db.iter_person_handles() 406 407 for option, value in self.options.handler.options_dict.items(): 408 exec('%s = %s' % (option, value), globals()) 409 # TODO my pylint doesn't seem to understand these variables really 410 # are defined here, so I have disabled the undefined-variable error 411 412 if self.v_r: 413 self.v_r.real_model.clear() 414 415 self.set_total(self.db.get_number_of_people() + 416 self.db.get_number_of_families()) 417 418 for person_handle in person_handles: 419 person = find_person(self.db, person_handle) 420 421 rule_list = [ 422 BirthAfterBapt(self.db, person), 423 DeathBeforeBapt(self.db, person), 424 BirthAfterBury(self.db, person), 425 DeathAfterBury(self.db, person), 426 BirthAfterDeath(self.db, person), 427 BaptAfterBury(self.db, person), 428 OldAge(self.db, person, oldage, estimate_age), 429 OldAgeButNoDeath(self.db, person, oldage, estimate_age), 430 UnknownGender(self.db, person), 431 MultipleParents(self.db, person), 432 MarriedOften(self.db, person, wedder), 433 OldUnmarried(self.db, person, oldunm, estimate_age), 434 TooManyChildren(self.db, person, mxchilddad, mxchildmom), 435 Disconnected(self.db, person), 436 InvalidBirthDate(self.db, person, invdate), 437 InvalidDeathDate(self.db, person, invdate), 438 BirthEqualsDeath(self.db, person), 439 BirthEqualsMarriage(self.db, person), 440 DeathEqualsMarriage(self.db, person), 441 ] 442 443 for rule in rule_list: 444 if rule.broken(): 445 self.add_results(rule.report_itself()) 446 447 clear_cache() 448 if not cli: 449 self.update() 450 451 # Family-based rules 452 for family_handle in self.db.iter_family_handles(): 453 family = find_family(self.db, family_handle) 454 455 rule_list = [ 456 SameSexFamily(self.db, family), 457 FemaleHusband(self.db, family), 458 MaleWife(self.db, family), 459 SameSurnameFamily(self.db, family), 460 LargeAgeGapFamily(self.db, family, hwdif, estimate_age), 461 MarriageBeforeBirth(self.db, family, estimate_age), 462 MarriageAfterDeath(self.db, family, estimate_age), 463 EarlyMarriage(self.db, family, yngmar, estimate_age), 464 LateMarriage(self.db, family, oldmar, estimate_age), 465 OldParent(self.db, family, oldmom, olddad, estimate_age), 466 YoungParent(self.db, family, yngmom, yngdad, estimate_age), 467 UnbornParent(self.db, family, estimate_age), 468 DeadParent(self.db, family, estimate_age), 469 LargeChildrenSpan(self.db, family, cbspan, estimate_age), 470 LargeChildrenAgeDiff(self.db, family, cspace, estimate_age), 471 MarriedRelation(self.db, family), 472 ] 473 474 for rule in rule_list: 475 if rule.broken(): 476 self.add_results(rule.report_itself()) 477 478 clear_cache() 479 if not cli: 480 self.update() 481 482#------------------------------------------------------------------------- 483# 484# Display the results 485# 486#------------------------------------------------------------------------- 487class VerifyResults(ManagedWindow): 488 """ GUI class to show the results in another dialog """ 489 IGNORE_COL = 0 490 WARNING_COL = 1 491 OBJ_ID_COL = 2 492 OBJ_NAME_COL = 3 493 OBJ_TYPE_COL = 4 494 RULE_ID_COL = 5 495 OBJ_HANDLE_COL = 6 496 FG_COLOR_COL = 7 497 TRUE_COL = 8 498 SHOW_COL = 9 499 500 def __init__(self, dbstate, uistate, track, glade, closeall): 501 """ initialize things """ 502 self.title = _('Data Verification Results') 503 504 ManagedWindow.__init__(self, uistate, track, self.__class__) 505 506 self.dbstate = dbstate 507 self.closeall = closeall 508 self._set_filename() 509 self.top = glade 510 window = self.top.get_object("verify_result") 511 self.set_window(window, self.top.get_object('title2'), self.title) 512 self.setup_configs('interface.verifyresults', 500, 300) 513 window.connect("close", self.close) 514 close_btn = self.top.get_object("closebutton1") 515 close_btn.connect("clicked", self.close) 516 517 self.warn_tree = self.top.get_object('warn_tree') 518 self.warn_tree.connect('button_press_event', self.double_click) 519 520 self.selection = self.warn_tree.get_selection() 521 522 self.hide_button = self.top.get_object('hide_button') 523 self.hide_button.connect('toggled', self.hide_toggled) 524 525 self.mark_button = self.top.get_object('mark_all') 526 self.mark_button.connect('clicked', self.mark_clicked) 527 528 self.unmark_button = self.top.get_object('unmark_all') 529 self.unmark_button.connect('clicked', self.unmark_clicked) 530 531 self.invert_button = self.top.get_object('invert_all') 532 self.invert_button.connect('clicked', self.invert_clicked) 533 534 self.real_model = Gtk.ListStore(GObject.TYPE_BOOLEAN, 535 GObject.TYPE_STRING, 536 GObject.TYPE_STRING, 537 GObject.TYPE_STRING, 538 GObject.TYPE_STRING, object, 539 GObject.TYPE_STRING, 540 GObject.TYPE_STRING, 541 GObject.TYPE_BOOLEAN, 542 GObject.TYPE_BOOLEAN) 543 self.filt_model = self.real_model.filter_new() 544 self.filt_model.set_visible_column(VerifyResults.TRUE_COL) 545 if hasattr(self.filt_model, "sort_new_with_model"): 546 self.sort_model = self.filt_model.sort_new_with_model() 547 else: 548 self.sort_model = Gtk.TreeModelSort.new_with_model(self.filt_model) 549 self.warn_tree.set_model(self.sort_model) 550 551 self.renderer = Gtk.CellRendererText() 552 self.img_renderer = Gtk.CellRendererPixbuf() 553 self.bool_renderer = Gtk.CellRendererToggle() 554 self.bool_renderer.connect('toggled', self.selection_toggled) 555 556 # Add ignore column 557 ignore_column = Gtk.TreeViewColumn(_('Mark'), self.bool_renderer, 558 active=VerifyResults.IGNORE_COL) 559 ignore_column.set_sort_column_id(VerifyResults.IGNORE_COL) 560 self.warn_tree.append_column(ignore_column) 561 562 # Add image column 563 img_column = Gtk.TreeViewColumn(None, self.img_renderer) 564 img_column.set_cell_data_func(self.img_renderer, self.get_image) 565 self.warn_tree.append_column(img_column) 566 567 # Add column with the warning text 568 warn_column = Gtk.TreeViewColumn(_('Warning'), self.renderer, 569 text=VerifyResults.WARNING_COL, 570 foreground=VerifyResults.FG_COLOR_COL) 571 warn_column.set_sort_column_id(VerifyResults.WARNING_COL) 572 self.warn_tree.append_column(warn_column) 573 574 # Add column with object gramps_id 575 id_column = Gtk.TreeViewColumn(_('ID'), self.renderer, 576 text=VerifyResults.OBJ_ID_COL, 577 foreground=VerifyResults.FG_COLOR_COL) 578 id_column.set_sort_column_id(VerifyResults.OBJ_ID_COL) 579 self.warn_tree.append_column(id_column) 580 581 # Add column with object name 582 name_column = Gtk.TreeViewColumn(_('Name'), self.renderer, 583 text=VerifyResults.OBJ_NAME_COL, 584 foreground=VerifyResults.FG_COLOR_COL) 585 name_column.set_sort_column_id(VerifyResults.OBJ_NAME_COL) 586 self.warn_tree.append_column(name_column) 587 588 self.show() 589 self.window_shown = False 590 591 def _set_filename(self): 592 """ set the file where people who will be ignored will be kept """ 593 db_filename = self.dbstate.db.get_save_path() 594 if isinstance(db_filename, str): 595 db_filename = db_filename.encode('utf-8') 596 md5sum = md5(db_filename) 597 self.ignores_filename = os.path.join( 598 VERSION_DIR, md5sum.hexdigest() + os.path.extsep + 'vfm') 599 600 def load_ignored(self, db_filename): 601 """ get ready to load the file with the previously-ignored people """ 602 ## a new Gramps major version means recreating the .vfm file. 603 ## User can copy over old one, with name of new one, but no guarantee 604 ## that will work. 605 if not self._load_ignored(self.ignores_filename): 606 self.ignores = {} 607 608 def _load_ignored(self, filename): 609 """ load the file with the people who were previously ignored """ 610 try: 611 try: 612 file = open(filename, 'rb') 613 except IOError: 614 return False 615 self.ignores = pickle.load(file) 616 file.close() 617 return True 618 except (IOError, EOFError): 619 file.close() 620 return False 621 622 def save_ignored(self, new_ignores): 623 """ get ready to save the file with the ignored people """ 624 self.ignores = new_ignores 625 self._save_ignored(self.ignores_filename) 626 627 def _save_ignored(self, filename): 628 """ save the file with the people the user wants to ignore """ 629 try: 630 with open(filename, 'wb') as file: 631 pickle.dump(self.ignores, file, 1) 632 return True 633 except IOError: 634 return False 635 636 def get_marking(self, handle, rule_id): 637 if handle in self.ignores: 638 return rule_id in self.ignores[handle] 639 else: 640 return False 641 642 def get_new_marking(self): 643 new_ignores = {} 644 for row_num in range(len(self.real_model)): 645 path = (row_num,) 646 row = self.real_model[path] 647 ignore = row[VerifyResults.IGNORE_COL] 648 if ignore: 649 handle = row[VerifyResults.OBJ_HANDLE_COL] 650 rule_id = row[VerifyResults.RULE_ID_COL] 651 if handle not in new_ignores: 652 new_ignores[handle] = set() 653 new_ignores[handle].add(rule_id) 654 return new_ignores 655 656 def close(self, *obj): 657 """ close the dialog and write out the file """ 658 new_ignores = self.get_new_marking() 659 self.save_ignored(new_ignores) 660 661 ManagedWindow.close(self, *obj) 662 self.closeall() 663 664 def hide_toggled(self, button): 665 self.filt_model = self.real_model.filter_new() 666 if button.get_active(): 667 button.set_label(_("_Show all")) 668 self.filt_model.set_visible_column(VerifyResults.SHOW_COL) 669 else: 670 button.set_label(_("_Hide marked")) 671 self.filt_model.set_visible_column(VerifyResults.TRUE_COL) 672 if hasattr(self.filt_model, "sort_new_with_model"): 673 self.sort_model = self.filt_model.sort_new_with_model() 674 else: 675 self.sort_model = Gtk.TreeModelSort.new_with_model( 676 self.filt_model) 677 self.warn_tree.set_model(self.sort_model) 678 679 def selection_toggled(self, cell, path_string): 680 sort_path = tuple(map(int, path_string.split(':'))) 681 filt_path = self.sort_model.convert_path_to_child_path( 682 Gtk.TreePath(sort_path)) 683 real_path = self.filt_model.convert_path_to_child_path(filt_path) 684 row = self.real_model[real_path] 685 row[VerifyResults.IGNORE_COL] = not row[VerifyResults.IGNORE_COL] 686 row[VerifyResults.SHOW_COL] = not row[VerifyResults.IGNORE_COL] 687 self.real_model.row_changed(real_path, row.iter) 688 689 def mark_clicked(self, mark_button): 690 for row_num in range(len(self.real_model)): 691 path = (row_num,) 692 row = self.real_model[path] 693 row[VerifyResults.IGNORE_COL] = True 694 row[VerifyResults.SHOW_COL] = False 695 self.filt_model.refilter() 696 697 def unmark_clicked(self, unmark_button): 698 for row_num in range(len(self.real_model)): 699 path = (row_num,) 700 row = self.real_model[path] 701 row[VerifyResults.IGNORE_COL] = False 702 row[VerifyResults.SHOW_COL] = True 703 self.filt_model.refilter() 704 705 def invert_clicked(self, invert_button): 706 for row_num in range(len(self.real_model)): 707 path = (row_num,) 708 row = self.real_model[path] 709 row[VerifyResults.IGNORE_COL] = not row[VerifyResults.IGNORE_COL] 710 row[VerifyResults.SHOW_COL] = not row[VerifyResults.SHOW_COL] 711 self.filt_model.refilter() 712 713 def double_click(self, obj, event): 714 """ the user wants to edit the selected person or family """ 715 if (event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS 716 and event.button == 1): 717 (model, node) = self.selection.get_selected() 718 if not node: 719 return 720 sort_path = self.sort_model.get_path(node) 721 filt_path = self.sort_model.convert_path_to_child_path(sort_path) 722 real_path = self.filt_model.convert_path_to_child_path(filt_path) 723 row = self.real_model[real_path] 724 the_type = row[VerifyResults.OBJ_TYPE_COL] 725 handle = row[VerifyResults.OBJ_HANDLE_COL] 726 if the_type == 'Person': 727 try: 728 person = self.dbstate.db.get_person_from_handle(handle) 729 EditPerson(self.dbstate, self.uistate, self.track, person) 730 except WindowActiveError: 731 pass 732 elif the_type == 'Family': 733 try: 734 family = self.dbstate.db.get_family_from_handle(handle) 735 EditFamily(self.dbstate, self.uistate, self.track, family) 736 except WindowActiveError: 737 pass 738 739 def get_image(self, column, cell, model, iter_, user_data=None): 740 """ flag whether each line is a person or family """ 741 the_type = model.get_value(iter_, VerifyResults.OBJ_TYPE_COL) 742 if the_type == 'Person': 743 cell.set_property('icon-name', 'gramps-person') 744 elif the_type == 'Family': 745 cell.set_property('icon-name', 'gramps-family') 746 747 def add_results(self, results): 748 (msg, gramps_id, name, the_type, rule_id, severity, handle) = results 749 ignore = self.get_marking(handle, rule_id) 750 if severity == Rule.ERROR: 751 line_color = 'red' 752 else: 753 line_color = None 754 self.real_model.append(row=[ignore, msg, gramps_id, name, 755 the_type, rule_id, handle, line_color, 756 True, not ignore]) 757 758 if not self.window_shown: 759 self.window.show() 760 self.window_shown = True 761 762 def build_menu_names(self, obj): 763 """ build the menu names """ 764 return (self.title, self.title) 765 766#------------------------------------------------------------------------ 767# 768# 769# 770#------------------------------------------------------------------------ 771class VerifyOptions(tool.ToolOptions): 772 """ 773 Defines options and provides handling interface. 774 """ 775 776 def __init__(self, name, person_id=None): 777 """ initialize the options """ 778 tool.ToolOptions.__init__(self, name, person_id) 779 780 # Options specific for this report 781 self.options_dict = { 782 'oldage' : 90, 783 'hwdif' : 30, 784 'cspace' : 8, 785 'cbspan' : 25, 786 'yngmar' : 17, 787 'oldmar' : 50, 788 'oldmom' : 48, 789 'yngmom' : 17, 790 'yngdad' : 18, 791 'olddad' : 65, 792 'wedder' : 3, 793 'mxchildmom' : 12, 794 'mxchilddad' : 15, 795 'lngwdw' : 30, 796 'oldunm' : 99, 797 'estimate_age' : 0, 798 'invdate' : 1, 799 } 800 # TODO these strings are defined in the glade file (more or less, since 801 # those have accelerators), and so are not translated here, but that 802 # means that a CLI user who runs gramps in a non-English language and 803 # says (for instance) "show=oldage" will see "Maximum age" in English 804 # (but I think such a CLI use is very unlikely and so is low priority, 805 # especially since the tool's normal CLI output will be translated) 806 self.options_help = { 807 'oldage' : ("=num", "Maximum age", "Age in years"), 808 'hwdif' : ("=num", "Maximum husband-wife age difference", 809 "Age difference in years"), 810 'cspace' : ("=num", 811 "Maximum number of years between children", 812 "Number of years"), 813 'cbspan' : ("=num", 814 "Maximum span of years for all children", 815 "Span in years"), 816 'yngmar' : ("=num", "Minimum age to marry", "Age in years"), 817 'oldmar' : ("=num", "Maximum age to marry", "Age in years"), 818 'oldmom' : ("=num", "Maximum age to bear a child", 819 "Age in years"), 820 'yngmom' : ("=num", "Minimum age to bear a child", 821 "Age in years"), 822 'yngdad' : ("=num", "Minimum age to father a child", 823 "Age in years"), 824 'olddad' : ("=num", "Maximum age to father a child", 825 "Age in years"), 826 'wedder' : ("=num", "Maximum number of spouses for a person", 827 "Number of spouses"), 828 'mxchildmom' : ("=num", "Maximum number of children for a woman", 829 "Number of children"), 830 'mxchilddad' : ("=num", "Maximum number of children for a man", 831 "Number of chidlren"), 832 'lngwdw' : ("=num", "Maximum number of consecutive years " 833 "of widowhood before next marriage", 834 "Number of years"), 835 'oldunm' : ("=num", "Maximum age for an unmarried person" 836 "Number of years"), 837 'estimate_age' : ("=0/1", 838 "Whether to estimate missing or inexact dates", 839 ["Do not estimate", "Estimate dates"], 840 True), 841 'invdate' : ("=0/1", "Whether to check for invalid dates" 842 "Do not identify invalid dates", 843 "Identify invalid dates", True), 844 } 845 846#------------------------------------------------------------------------- 847# 848# Base classes for different tests -- the rules 849# 850#------------------------------------------------------------------------- 851class Rule: 852 """ 853 Basic class for use in this tool. 854 855 Other rules must inherit from this. 856 """ 857 ID = 0 858 TYPE = '' 859 860 ERROR = 1 861 WARNING = 2 862 863 SEVERITY = WARNING 864 865 def __init__(self, db, obj): 866 """ initialize the rule """ 867 self.db = db 868 self.obj = obj 869 870 def broken(self): 871 """ 872 Return boolean indicating whether this rule is violated. 873 """ 874 return False 875 876 def get_message(self): 877 """ return the rule's error message """ 878 assert False, "Need to be overriden in the derived class" 879 880 def get_name(self): 881 """ return the person's primary name or the name of the family """ 882 assert False, "Need to be overriden in the derived class" 883 884 def get_handle(self): 885 """ return the object's handle """ 886 return self.obj.handle 887 888 def get_id(self): 889 """ return the object's gramps_id """ 890 return self.obj.gramps_id 891 892 def get_rule_id(self): 893 """ return the rule's identification number, and parameters """ 894 params = self._get_params() 895 return (self.ID, params) 896 897 def _get_params(self): 898 """ return the rule's parameters """ 899 return tuple() 900 901 def report_itself(self): 902 """ return the details about a rule """ 903 handle = self.get_handle() 904 the_type = self.TYPE 905 rule_id = self.get_rule_id() 906 severity = self.SEVERITY 907 name = self.get_name() 908 gramps_id = self.get_id() 909 msg = self.get_message() 910 return (msg, gramps_id, name, the_type, rule_id, severity, handle) 911 912class PersonRule(Rule): 913 """ 914 Person-based class. 915 """ 916 TYPE = 'Person' 917 def get_name(self): 918 """ return the person's primary name """ 919 return self.obj.get_primary_name().get_name() 920 921class FamilyRule(Rule): 922 """ 923 Family-based class. 924 """ 925 TYPE = 'Family' 926 def get_name(self): 927 """ return the name of the family """ 928 return family_name(self.obj, self.db) 929 930#------------------------------------------------------------------------- 931# 932# Actual rules for testing 933# 934#------------------------------------------------------------------------- 935class BirthAfterBapt(PersonRule): 936 """ test if a person was baptised before their birth """ 937 ID = 1 938 SEVERITY = Rule.ERROR 939 def broken(self): 940 """ return boolean indicating whether this rule is violated """ 941 birth_date = get_birth_date(self.db, self.obj) 942 bapt_date = get_bapt_date(self.db, self.obj) 943 birth_ok = birth_date > 0 if birth_date is not None else False 944 bapt_ok = bapt_date > 0 if bapt_date is not None else False 945 return birth_ok and bapt_ok and birth_date > bapt_date 946 947 def get_message(self): 948 """ return the rule's error message """ 949 return _("Baptism before birth") 950 951class DeathBeforeBapt(PersonRule): 952 """ test if a person died before their baptism """ 953 ID = 2 954 SEVERITY = Rule.ERROR 955 def broken(self): 956 """ return boolean indicating whether this rule is violated """ 957 death_date = get_death_date(self.db, self.obj) 958 bapt_date = get_bapt_date(self.db, self.obj) 959 bapt_ok = bapt_date > 0 if bapt_date is not None else False 960 death_ok = death_date > 0 if death_date is not None else False 961 return death_ok and bapt_ok and bapt_date > death_date 962 963 def get_message(self): 964 """ return the rule's error message """ 965 return _("Death before baptism") 966 967class BirthAfterBury(PersonRule): 968 """ test if a person was buried before their birth """ 969 ID = 3 970 SEVERITY = Rule.ERROR 971 def broken(self): 972 """ return boolean indicating whether this rule is violated """ 973 birth_date = get_birth_date(self.db, self.obj) 974 bury_date = get_bury_date(self.db, self.obj) 975 birth_ok = birth_date > 0 if birth_date is not None else False 976 bury_ok = bury_date > 0 if bury_date is not None else False 977 return birth_ok and bury_ok and birth_date > bury_date 978 979 def get_message(self): 980 """ return the rule's error message """ 981 return _("Burial before birth") 982 983class DeathAfterBury(PersonRule): 984 """ test if a person was buried before their death """ 985 ID = 4 986 SEVERITY = Rule.ERROR 987 def broken(self): 988 """ return boolean indicating whether this rule is violated """ 989 death_date = get_death_date(self.db, self.obj) 990 bury_date = get_bury_date(self.db, self.obj) 991 death_ok = death_date > 0 if death_date is not None else False 992 bury_ok = bury_date > 0 if bury_date is not None else False 993 return death_ok and bury_ok and death_date > bury_date 994 995 def get_message(self): 996 """ return the rule's error message """ 997 return _("Burial before death") 998 999class BirthAfterDeath(PersonRule): 1000 """ test if a person died before their birth """ 1001 ID = 5 1002 SEVERITY = Rule.ERROR 1003 def broken(self): 1004 """ return boolean indicating whether this rule is violated """ 1005 birth_date = get_birth_date(self.db, self.obj) 1006 death_date = get_death_date(self.db, self.obj) 1007 birth_ok = birth_date > 0 if birth_date is not None else False 1008 death_ok = death_date > 0 if death_date is not None else False 1009 return birth_ok and death_ok and birth_date > death_date 1010 1011 def get_message(self): 1012 """ return the rule's error message """ 1013 return _("Death before birth") 1014 1015class BaptAfterBury(PersonRule): 1016 """ test if a person was buried before their baptism """ 1017 ID = 6 1018 SEVERITY = Rule.ERROR 1019 def broken(self): 1020 """ return boolean indicating whether this rule is violated """ 1021 bapt_date = get_bapt_date(self.db, self.obj) 1022 bury_date = get_bury_date(self.db, self.obj) 1023 bapt_ok = bapt_date > 0 if bapt_date is not None else False 1024 bury_ok = bury_date > 0 if bury_date is not None else False 1025 return bapt_ok and bury_ok and bapt_date > bury_date 1026 1027 def get_message(self): 1028 """ return the rule's error message """ 1029 return _("Burial before baptism") 1030 1031class OldAge(PersonRule): 1032 """ test if a person died beyond the age the user has set """ 1033 ID = 7 1034 SEVERITY = Rule.WARNING 1035 def __init__(self, db, person, old_age, est): 1036 """ initialize the rule """ 1037 PersonRule.__init__(self, db, person) 1038 self.old_age = old_age 1039 self.est = est 1040 1041 def _get_params(self): 1042 """ return the rule's parameters """ 1043 return (self.old_age, self.est) 1044 1045 def broken(self): 1046 """ return boolean indicating whether this rule is violated """ 1047 age_at_death = get_age_at_death(self.db, self.obj, self.est) 1048 return age_at_death / 365 > self.old_age 1049 1050 def get_message(self): 1051 """ return the rule's error message """ 1052 return _("Old age at death") 1053 1054class UnknownGender(PersonRule): 1055 """ test if a person is neither a male nor a female """ 1056 ID = 8 1057 SEVERITY = Rule.WARNING 1058 def broken(self): 1059 """ return boolean indicating whether this rule is violated """ 1060 female = self.obj.get_gender() == Person.FEMALE 1061 male = self.obj.get_gender() == Person.MALE 1062 return not (male or female) 1063 1064 def get_message(self): 1065 """ return the rule's error message """ 1066 return _("Unknown gender") 1067 1068class MultipleParents(PersonRule): 1069 """ test if a person belongs to multiple families """ 1070 ID = 9 1071 SEVERITY = Rule.WARNING 1072 def broken(self): 1073 """ return boolean indicating whether this rule is violated """ 1074 n_parent_sets = len(self.obj.get_parent_family_handle_list()) 1075 return n_parent_sets > 1 1076 1077 def get_message(self): 1078 """ return the rule's error message """ 1079 return _("Multiple parents") 1080 1081class MarriedOften(PersonRule): 1082 """ test if a person was married 'often' """ 1083 ID = 10 1084 SEVERITY = Rule.WARNING 1085 def __init__(self, db, person, wedder): 1086 """ initialize the rule """ 1087 PersonRule.__init__(self, db, person) 1088 self.wedder = wedder 1089 1090 def _get_params(self): 1091 """ return the rule's parameters """ 1092 return (self.wedder,) 1093 1094 def broken(self): 1095 """ return boolean indicating whether this rule is violated """ 1096 n_spouses = len(self.obj.get_family_handle_list()) 1097 return n_spouses > self.wedder 1098 1099 def get_message(self): 1100 """ return the rule's error message """ 1101 return _("Married often") 1102 1103class OldUnmarried(PersonRule): 1104 """ test if a person was married when they died """ 1105 ID = 11 1106 SEVERITY = Rule.WARNING 1107 def __init__(self, db, person, old_unm, est): 1108 """ initialize the rule """ 1109 PersonRule.__init__(self, db, person) 1110 self.old_unm = old_unm 1111 self.est = est 1112 1113 def _get_params(self): 1114 """ return the rule's parameters """ 1115 return (self.old_unm, self.est) 1116 1117 def broken(self): 1118 """ return boolean indicating whether this rule is violated """ 1119 age_at_death = get_age_at_death(self.db, self.obj, self.est) 1120 n_spouses = len(self.obj.get_family_handle_list()) 1121 return age_at_death / 365 > self.old_unm and n_spouses == 0 1122 1123 def get_message(self): 1124 """ return the rule's error message """ 1125 return _("Old and unmarried") 1126 1127class TooManyChildren(PersonRule): 1128 """ test if a person had 'too many' children """ 1129 ID = 12 1130 SEVERITY = Rule.WARNING 1131 def __init__(self, db, obj, mx_child_dad, mx_child_mom): 1132 """ initialize the rule """ 1133 PersonRule.__init__(self, db, obj) 1134 self.mx_child_dad = mx_child_dad 1135 self.mx_child_mom = mx_child_mom 1136 1137 def _get_params(self): 1138 """ return the rule's parameters """ 1139 return (self.mx_child_dad, self.mx_child_mom) 1140 1141 def broken(self): 1142 """ return boolean indicating whether this rule is violated """ 1143 n_child = get_n_children(self.db, self.obj) 1144 1145 if (self.obj.get_gender == Person.MALE 1146 and n_child > self.mx_child_dad): 1147 return True 1148 1149 if (self.obj.get_gender == Person.FEMALE 1150 and n_child > self.mx_child_mom): 1151 return True 1152 1153 return False 1154 1155 def get_message(self): 1156 """ return the rule's error message """ 1157 return _("Too many children") 1158 1159class SameSexFamily(FamilyRule): 1160 """ test if a family's parents are both male or both female """ 1161 ID = 13 1162 SEVERITY = Rule.WARNING 1163 def broken(self): 1164 """ return boolean indicating whether this rule is violated """ 1165 mother = get_mother(self.db, self.obj) 1166 father = get_father(self.db, self.obj) 1167 same_sex = (mother and father and 1168 (mother.get_gender() == father.get_gender())) 1169 unknown_sex = (mother and 1170 (mother.get_gender() == Person.UNKNOWN)) 1171 return same_sex and not unknown_sex 1172 1173 def get_message(self): 1174 """ return the rule's error message """ 1175 return _("Same sex marriage") 1176 1177class FemaleHusband(FamilyRule): 1178 """ test if a family's 'husband' is female """ 1179 ID = 14 1180 SEVERITY = Rule.WARNING 1181 def broken(self): 1182 """ return boolean indicating whether this rule is violated """ 1183 father = get_father(self.db, self.obj) 1184 return father and (father.get_gender() == Person.FEMALE) 1185 1186 def get_message(self): 1187 """ return the rule's error message """ 1188 return _("Female husband") 1189 1190class MaleWife(FamilyRule): 1191 """ test if a family's 'wife' is male """ 1192 ID = 15 1193 SEVERITY = Rule.WARNING 1194 def broken(self): 1195 """ return boolean indicating whether this rule is violated """ 1196 mother = get_mother(self.db, self.obj) 1197 return mother and (mother.get_gender() == Person.MALE) 1198 1199 def get_message(self): 1200 """ return the rule's error message """ 1201 return _("Male wife") 1202 1203class SameSurnameFamily(FamilyRule): 1204 """ test if a family's parents were born with the same surname """ 1205 ID = 16 1206 SEVERITY = Rule.WARNING 1207 def broken(self): 1208 """ return boolean indicating whether this rule is violated """ 1209 mother = get_mother(self.db, self.obj) 1210 father = get_father(self.db, self.obj) 1211 _broken = False 1212 1213 # Make sure both mother and father exist. 1214 if mother and father: 1215 mname = mother.get_primary_name() 1216 fname = father.get_primary_name() 1217 # Only compare birth names (not married names). 1218 if (mname.get_type() == NameType.BIRTH 1219 and fname.get_type() == NameType.BIRTH): 1220 # Empty names don't count. 1221 if (len(mname.get_surname()) != 0 1222 and len(fname.get_surname()) != 0): 1223 # Finally, check if the names are the same. 1224 if mname.get_surname() == fname.get_surname(): 1225 _broken = True 1226 1227 return _broken 1228 1229 def get_message(self): 1230 """ return the rule's error message """ 1231 return _("Husband and wife with the same surname") 1232 1233class LargeAgeGapFamily(FamilyRule): 1234 """ test if a family's parents were born far apart """ 1235 ID = 17 1236 SEVERITY = Rule.WARNING 1237 def __init__(self, db, obj, hw_diff, est): 1238 """ initialize the rule """ 1239 FamilyRule.__init__(self, db, obj) 1240 self.hw_diff = hw_diff 1241 self.est = est 1242 1243 def _get_params(self): 1244 """ return the rule's parameters """ 1245 return (self.hw_diff, self.est) 1246 1247 def broken(self): 1248 """ return boolean indicating whether this rule is violated """ 1249 mother = get_mother(self.db, self.obj) 1250 father = get_father(self.db, self.obj) 1251 mother_birth_date = get_birth_date(self.db, mother, self.est) 1252 father_birth_date = get_birth_date(self.db, father, self.est) 1253 mother_birth_date_ok = mother_birth_date > 0 1254 father_birth_date_ok = father_birth_date > 0 1255 large_diff = abs( 1256 father_birth_date-mother_birth_date) / 365 > self.hw_diff 1257 return mother_birth_date_ok and father_birth_date_ok and large_diff 1258 1259 def get_message(self): 1260 """ return the rule's error message """ 1261 return _("Large age difference between spouses") 1262 1263class MarriageBeforeBirth(FamilyRule): 1264 """ test if each family's parent was born before the marriage """ 1265 ID = 18 1266 SEVERITY = Rule.ERROR 1267 def __init__(self, db, obj, est): 1268 """ initialize the rule """ 1269 FamilyRule.__init__(self, db, obj) 1270 self.est = est 1271 1272 def _get_params(self): 1273 """ return the rule's parameters """ 1274 return (self.est,) 1275 1276 def broken(self): 1277 """ return boolean indicating whether this rule is violated """ 1278 marr_date = get_marriage_date(self.db, self.obj) 1279 marr_date_ok = marr_date > 0 1280 1281 mother = get_mother(self.db, self.obj) 1282 father = get_father(self.db, self.obj) 1283 mother_birth_date = get_birth_date(self.db, mother, self.est) 1284 father_birth_date = get_birth_date(self.db, father, self.est) 1285 mother_birth_date_ok = mother_birth_date > 0 1286 father_birth_date_ok = father_birth_date > 0 1287 1288 father_broken = (father_birth_date_ok and marr_date_ok 1289 and (father_birth_date > marr_date)) 1290 mother_broken = (mother_birth_date_ok and marr_date_ok 1291 and (mother_birth_date > marr_date)) 1292 1293 return father_broken or mother_broken 1294 1295 def get_message(self): 1296 """ return the rule's error message """ 1297 return _("Marriage before birth") 1298 1299class MarriageAfterDeath(FamilyRule): 1300 """ test if each family's parent died before the marriage """ 1301 ID = 19 1302 SEVERITY = Rule.ERROR 1303 def __init__(self, db, obj, est): 1304 """ initialize the rule """ 1305 FamilyRule.__init__(self, db, obj) 1306 self.est = est 1307 1308 def _get_params(self): 1309 """ return the rule's parameters """ 1310 return (self.est,) 1311 1312 def broken(self): 1313 """ return boolean indicating whether this rule is violated """ 1314 marr_date = get_marriage_date(self.db, self.obj) 1315 marr_date_ok = marr_date > 0 1316 1317 mother = get_mother(self.db, self.obj) 1318 father = get_father(self.db, self.obj) 1319 mother_death_date = get_death_date(self.db, mother, self.est) 1320 father_death_date = get_death_date(self.db, father, self.est) 1321 mother_death_date_ok = mother_death_date > 0 1322 father_death_date_ok = father_death_date > 0 1323 1324 father_broken = (father_death_date_ok and marr_date_ok 1325 and (father_death_date < marr_date)) 1326 mother_broken = (mother_death_date_ok and marr_date_ok 1327 and (mother_death_date < marr_date)) 1328 1329 return father_broken or mother_broken 1330 1331 def get_message(self): 1332 """ return the rule's error message """ 1333 return _("Marriage after death") 1334 1335class EarlyMarriage(FamilyRule): 1336 """ test if each family's parent was 'too young' at the marriage """ 1337 ID = 20 1338 SEVERITY = Rule.WARNING 1339 def __init__(self, db, obj, yng_mar, est): 1340 """ initialize the rule """ 1341 FamilyRule.__init__(self, db, obj) 1342 self.yng_mar = yng_mar 1343 self.est = est 1344 1345 def _get_params(self): 1346 """ return the rule's parameters """ 1347 return (self.yng_mar, self.est,) 1348 1349 def broken(self): 1350 """ return boolean indicating whether this rule is violated """ 1351 marr_date = get_marriage_date(self.db, self.obj) 1352 marr_date_ok = marr_date > 0 1353 1354 mother = get_mother(self.db, self.obj) 1355 father = get_father(self.db, self.obj) 1356 mother_birth_date = get_birth_date(self.db, mother, self.est) 1357 father_birth_date = get_birth_date(self.db, father, self.est) 1358 mother_birth_date_ok = mother_birth_date > 0 1359 father_birth_date_ok = father_birth_date > 0 1360 1361 father_broken = ( 1362 father_birth_date_ok and marr_date_ok and 1363 father_birth_date < marr_date and 1364 ((marr_date - father_birth_date) / 365 < self.yng_mar)) 1365 mother_broken = ( 1366 mother_birth_date_ok and marr_date_ok and 1367 mother_birth_date < marr_date and 1368 ((marr_date - mother_birth_date) / 365 < self.yng_mar)) 1369 1370 return father_broken or mother_broken 1371 1372 def get_message(self): 1373 """ return the rule's error message """ 1374 return _("Early marriage") 1375 1376class LateMarriage(FamilyRule): 1377 """ test if each family's parent was 'too old' at the marriage """ 1378 ID = 21 1379 SEVERITY = Rule.WARNING 1380 def __init__(self, db, obj, old_mar, est): 1381 """ initialize the rule """ 1382 FamilyRule.__init__(self, db, obj) 1383 self.old_mar = old_mar 1384 self.est = est 1385 1386 def _get_params(self): 1387 """ return the rule's parameters """ 1388 return (self.old_mar, self.est) 1389 1390 def broken(self): 1391 """ return boolean indicating whether this rule is violated """ 1392 marr_date = get_marriage_date(self.db, self.obj) 1393 marr_date_ok = marr_date > 0 1394 1395 mother = get_mother(self.db, self.obj) 1396 father = get_father(self.db, self.obj) 1397 mother_birth_date = get_birth_date(self.db, mother, self.est) 1398 father_birth_date = get_birth_date(self.db, father, self.est) 1399 mother_birth_date_ok = mother_birth_date > 0 1400 father_birth_date_ok = father_birth_date > 0 1401 1402 father_broken = ( 1403 father_birth_date_ok and marr_date_ok and 1404 ((marr_date - father_birth_date) / 365 > self.old_mar)) 1405 mother_broken = ( 1406 mother_birth_date_ok and marr_date_ok and 1407 ((marr_date - mother_birth_date) / 365 > self.old_mar)) 1408 1409 return father_broken or mother_broken 1410 1411 def get_message(self): 1412 """ return the rule's error message """ 1413 return _("Late marriage") 1414 1415class OldParent(FamilyRule): 1416 """ test if each family's parent was 'too old' at a child's birth """ 1417 ID = 22 1418 SEVERITY = Rule.WARNING 1419 def __init__(self, db, obj, old_mom, old_dad, est): 1420 """ initialize the rule """ 1421 FamilyRule.__init__(self, db, obj) 1422 self.old_mom = old_mom 1423 self.old_dad = old_dad 1424 self.est = est 1425 1426 def _get_params(self): 1427 """ return the rule's parameters """ 1428 return (self.old_mom, self.old_dad, self.est) 1429 1430 def broken(self): 1431 """ return boolean indicating whether this rule is violated """ 1432 mother = get_mother(self.db, self.obj) 1433 father = get_father(self.db, self.obj) 1434 mother_birth_date = get_birth_date(self.db, mother, self.est) 1435 father_birth_date = get_birth_date(self.db, father, self.est) 1436 mother_birth_date_ok = mother_birth_date > 0 1437 father_birth_date_ok = father_birth_date > 0 1438 1439 for child_ref in self.obj.get_child_ref_list(): 1440 child = find_person(self.db, child_ref.ref) 1441 child_birth_date = get_birth_date(self.db, child, self.est) 1442 child_birth_date_ok = child_birth_date > 0 1443 if not child_birth_date_ok: 1444 continue 1445 father_broken = ( 1446 father_birth_date_ok and 1447 ((child_birth_date - father_birth_date) / 365 > self.old_dad)) 1448 if father_broken: 1449 self.get_message = self.father_message 1450 return True 1451 1452 mother_broken = ( 1453 mother_birth_date_ok and 1454 ((child_birth_date - mother_birth_date) / 365 > self.old_mom)) 1455 if mother_broken: 1456 self.get_message = self.mother_message 1457 return True 1458 return False 1459 1460 def father_message(self): 1461 """ return the rule's error message """ 1462 return _("Old father") 1463 1464 def mother_message(self): 1465 """ return the rule's error message """ 1466 return _("Old mother") 1467 1468class YoungParent(FamilyRule): 1469 """ test if each family's parent was 'too young' at a child's birth """ 1470 ID = 23 1471 SEVERITY = Rule.WARNING 1472 def __init__(self, db, obj, yng_mom, yng_dad, est): 1473 """ initialize the rule """ 1474 FamilyRule.__init__(self, db, obj) 1475 self.yng_dad = yng_dad 1476 self.yng_mom = yng_mom 1477 self.est = est 1478 1479 def _get_params(self): 1480 """ return the rule's parameters """ 1481 return (self.yng_mom, self.yng_dad, self.est) 1482 1483 def broken(self): 1484 """ return boolean indicating whether this rule is violated """ 1485 mother = get_mother(self.db, self.obj) 1486 father = get_father(self.db, self.obj) 1487 mother_birth_date = get_birth_date(self.db, mother, self.est) 1488 father_birth_date = get_birth_date(self.db, father, self.est) 1489 mother_birth_date_ok = mother_birth_date > 0 1490 father_birth_date_ok = father_birth_date > 0 1491 1492 for child_ref in self.obj.get_child_ref_list(): 1493 child = find_person(self.db, child_ref.ref) 1494 child_birth_date = get_birth_date(self.db, child, self.est) 1495 child_birth_date_ok = child_birth_date > 0 1496 if not child_birth_date_ok: 1497 continue 1498 father_broken = ( 1499 father_birth_date_ok and 1500 ((child_birth_date - father_birth_date) / 365 < self.yng_dad)) 1501 if father_broken: 1502 self.get_message = self.father_message 1503 return True 1504 1505 mother_broken = ( 1506 mother_birth_date_ok and 1507 ((child_birth_date - mother_birth_date) / 365 < self.yng_mom)) 1508 if mother_broken: 1509 self.get_message = self.mother_message 1510 return True 1511 return False 1512 1513 def father_message(self): 1514 """ return the rule's error message """ 1515 return _("Young father") 1516 1517 def mother_message(self): 1518 """ return the rule's error message """ 1519 return _("Young mother") 1520 1521class UnbornParent(FamilyRule): 1522 """ test if each family's parent was not yet born at a child's birth """ 1523 ID = 24 1524 SEVERITY = Rule.ERROR 1525 def __init__(self, db, obj, est): 1526 """ initialize the rule """ 1527 FamilyRule.__init__(self, db, obj) 1528 self.est = est 1529 1530 def _get_params(self): 1531 """ return the rule's parameters """ 1532 return (self.est,) 1533 1534 def broken(self): 1535 """ return boolean indicating whether this rule is violated """ 1536 mother = get_mother(self.db, self.obj) 1537 father = get_father(self.db, self.obj) 1538 mother_birth_date = get_birth_date(self.db, mother, self.est) 1539 father_birth_date = get_birth_date(self.db, father, self.est) 1540 mother_birth_date_ok = mother_birth_date > 0 1541 father_birth_date_ok = father_birth_date > 0 1542 1543 for child_ref in self.obj.get_child_ref_list(): 1544 child = find_person(self.db, child_ref.ref) 1545 child_birth_date = get_birth_date(self.db, child, self.est) 1546 child_birth_date_ok = child_birth_date > 0 1547 if not child_birth_date_ok: 1548 continue 1549 father_broken = (father_birth_date_ok 1550 and (father_birth_date > child_birth_date)) 1551 if father_broken: 1552 self.get_message = self.father_message 1553 return True 1554 1555 mother_broken = (mother_birth_date_ok 1556 and (mother_birth_date > child_birth_date)) 1557 if mother_broken: 1558 self.get_message = self.mother_message 1559 return True 1560 1561 def father_message(self): 1562 """ return the rule's error message """ 1563 return _("Unborn father") 1564 1565 def mother_message(self): 1566 """ return the rule's error message """ 1567 return _("Unborn mother") 1568 1569class DeadParent(FamilyRule): 1570 """ test if each family's parent was dead at a child's birth """ 1571 ID = 25 1572 SEVERITY = Rule.ERROR 1573 def __init__(self, db, obj, est): 1574 """ initialize the rule """ 1575 FamilyRule.__init__(self, db, obj) 1576 self.est = est 1577 1578 def _get_params(self): 1579 """ return the rule's parameters """ 1580 return (self.est,) 1581 1582 def broken(self): 1583 """ return boolean indicating whether this rule is violated """ 1584 mother = get_mother(self.db, self.obj) 1585 father = get_father(self.db, self.obj) 1586 mother_death_date = get_death_date(self.db, mother, self.est) 1587 father_death_date = get_death_date(self.db, father, self.est) 1588 mother_death_date_ok = mother_death_date > 0 1589 father_death_date_ok = father_death_date > 0 1590 1591 for child_ref in self.obj.get_child_ref_list(): 1592 child = find_person(self.db, child_ref.ref) 1593 child_birth_date = get_birth_date(self.db, child, self.est) 1594 child_birth_date_ok = child_birth_date > 0 1595 if not child_birth_date_ok: 1596 continue 1597 1598 has_birth_rel_to_mother = child_ref.mrel == ChildRefType.BIRTH 1599 has_birth_rel_to_father = child_ref.frel == ChildRefType.BIRTH 1600 1601 father_broken = ( 1602 has_birth_rel_to_father 1603 and father_death_date_ok 1604 and ((father_death_date + 294) < child_birth_date)) 1605 if father_broken: 1606 self.get_message = self.father_message 1607 return True 1608 1609 mother_broken = (has_birth_rel_to_mother 1610 and mother_death_date_ok 1611 and (mother_death_date < child_birth_date)) 1612 if mother_broken: 1613 self.get_message = self.mother_message 1614 return True 1615 1616 def father_message(self): 1617 """ return the rule's error message """ 1618 return _("Dead father") 1619 1620 def mother_message(self): 1621 """ return the rule's error message """ 1622 return _("Dead mother") 1623 1624class LargeChildrenSpan(FamilyRule): 1625 """ test if a family's first and last children were born far apart """ 1626 ID = 26 1627 SEVERITY = Rule.WARNING 1628 def __init__(self, db, obj, cb_span, est): 1629 """ initialize the rule """ 1630 FamilyRule.__init__(self, db, obj) 1631 self.cbs = cb_span 1632 self.est = est 1633 1634 def _get_params(self): 1635 """ return the rule's parameters """ 1636 return (self.cbs, self.est) 1637 1638 def broken(self): 1639 """ return boolean indicating whether this rule is violated """ 1640 child_birh_dates = get_child_birth_dates(self.db, self.obj, self.est) 1641 child_birh_dates.sort() 1642 1643 return (child_birh_dates and 1644 ((child_birh_dates[-1] - child_birh_dates[0]) / 365 > self.cbs)) 1645 1646 def get_message(self): 1647 """ return the rule's error message """ 1648 return _("Large year span for all children") 1649 1650class LargeChildrenAgeDiff(FamilyRule): 1651 """ test if any of a family's children were born far apart """ 1652 ID = 27 1653 SEVERITY = Rule.WARNING 1654 def __init__(self, db, obj, c_space, est): 1655 """ initialize the rule """ 1656 FamilyRule.__init__(self, db, obj) 1657 self.c_space = c_space 1658 self.est = est 1659 1660 def _get_params(self): 1661 """ return the rule's parameters """ 1662 return (self.c_space, self.est) 1663 1664 def broken(self): 1665 """ return boolean indicating whether this rule is violated """ 1666 child_birh_dates = get_child_birth_dates(self.db, self.obj, self.est) 1667 child_birh_dates_diff = [child_birh_dates[i+1] - child_birh_dates[i] 1668 for i in range(len(child_birh_dates)-1)] 1669 1670 return (child_birh_dates_diff and 1671 max(child_birh_dates_diff) / 365 > self.c_space) 1672 1673 def get_message(self): 1674 """ return the rule's error message """ 1675 return _("Large age differences between children") 1676 1677class Disconnected(PersonRule): 1678 """ test if a person has no children and no parents """ 1679 ID = 28 1680 SEVERITY = Rule.WARNING 1681 def broken(self): 1682 """ return boolean indicating whether this rule is violated """ 1683 return (len(self.obj.get_parent_family_handle_list()) 1684 + len(self.obj.get_family_handle_list()) == 0) 1685 1686 def get_message(self): 1687 """ return the rule's error message """ 1688 return _("Disconnected individual") 1689 1690class InvalidBirthDate(PersonRule): 1691 """ test if a person has an 'invalid' birth date """ 1692 ID = 29 1693 SEVERITY = Rule.ERROR 1694 def __init__(self, db, person, invdate): 1695 """ initialize the rule """ 1696 PersonRule.__init__(self, db, person) 1697 self._invdate = invdate 1698 1699 def broken(self): 1700 """ return boolean indicating whether this rule is violated """ 1701 if not self._invdate: # should we check? 1702 return False 1703 # if so, let's get the birth date 1704 person = self.obj 1705 birth_ref = person.get_birth_ref() 1706 if birth_ref: 1707 birth_event = self.db.get_event_from_handle(birth_ref.ref) 1708 birth_date = birth_event.get_date_object() 1709 if birth_date and not birth_date.get_valid(): 1710 return True 1711 return False 1712 1713 def get_message(self): 1714 """ return the rule's error message """ 1715 return _("Invalid birth date") 1716 1717class InvalidDeathDate(PersonRule): 1718 """ test if a person has an 'invalid' death date """ 1719 ID = 30 1720 SEVERITY = Rule.ERROR 1721 def __init__(self, db, person, invdate): 1722 """ initialize the rule """ 1723 PersonRule.__init__(self, db, person) 1724 self._invdate = invdate 1725 1726 def broken(self): 1727 """ return boolean indicating whether this rule is violated """ 1728 if not self._invdate: # should we check? 1729 return False 1730 # if so, let's get the death date 1731 person = self.obj 1732 death_ref = person.get_death_ref() 1733 if death_ref: 1734 death_event = self.db.get_event_from_handle(death_ref.ref) 1735 death_date = death_event.get_date_object() 1736 if death_date and not death_date.get_valid(): 1737 return True 1738 return False 1739 1740 def get_message(self): 1741 """ return the rule's error message """ 1742 return _("Invalid death date") 1743 1744class MarriedRelation(FamilyRule): 1745 """ test if a family has a marriage date but is not marked 'married' """ 1746 ID = 31 1747 SEVERITY = Rule.WARNING 1748 def __init__(self, db, obj): 1749 """ initialize the rule """ 1750 FamilyRule.__init__(self, db, obj) 1751 1752 def broken(self): 1753 """ return boolean indicating whether this rule is violated """ 1754 marr_date = get_marriage_date(self.db, self.obj) 1755 marr_date_ok = marr_date > 0 1756 married = self.obj.get_relationship() == FamilyRelType.MARRIED 1757 if not married and marr_date_ok: 1758 return self.get_message 1759 1760 def get_message(self): 1761 """ return the rule's error message """ 1762 return _("Marriage date but not married") 1763 1764class OldAgeButNoDeath(PersonRule): 1765 """ test if a person is 'too old' but is not shown as dead """ 1766 ID = 32 1767 SEVERITY = Rule.WARNING 1768 def __init__(self, db, person, old_age, est): 1769 """ initialize the rule """ 1770 PersonRule.__init__(self, db, person) 1771 self.old_age = old_age 1772 self.est = est 1773 1774 def _get_params(self): 1775 """ return the rule's parameters """ 1776 return (self.old_age, self.est) 1777 1778 def broken(self): 1779 """ return boolean indicating whether this rule is violated """ 1780 birth_date = get_birth_date(self.db, self.obj, self.est) 1781 dead = get_death(self.db, self.obj) 1782 death_date = get_death_date(self.db, self.obj, True) # or burial date 1783 if dead or death_date or not birth_date: 1784 return 0 1785 age = (_today - birth_date) / 365 1786 return age > self.old_age 1787 1788 def get_message(self): 1789 """ return the rule's error message """ 1790 return _("Old age but no death") 1791 1792class BirthEqualsDeath(PersonRule): 1793 """ test if a person's birth date is the same as their death date """ 1794 ID = 33 1795 SEVERITY = Rule.WARNING 1796 def broken(self): 1797 """ return boolean indicating whether this rule is violated """ 1798 birth_date = get_birth_date(self.db, self.obj) 1799 death_date = get_death_date(self.db, self.obj) 1800 birth_ok = birth_date > 0 if birth_date is not None else False 1801 death_ok = death_date > 0 if death_date is not None else False 1802 return death_ok and birth_ok and birth_date == death_date 1803 1804 def get_message(self): 1805 """ return the rule's error message """ 1806 return _("Birth equals death") 1807 1808class BirthEqualsMarriage(PersonRule): 1809 """ test if a person's birth date is the same as their marriage date """ 1810 ID = 34 1811 SEVERITY = Rule.ERROR 1812 def broken(self): 1813 """ return boolean indicating whether this rule is violated """ 1814 birth_date = get_birth_date(self.db, self.obj) 1815 birth_ok = birth_date > 0 if birth_date is not None else False 1816 for fhandle in self.obj.get_family_handle_list(): 1817 family = self.db.get_family_from_handle(fhandle) 1818 marr_date = get_marriage_date(self.db, family) 1819 marr_ok = marr_date > 0 if marr_date is not None else False 1820 return marr_ok and birth_ok and birth_date == marr_date 1821 1822 def get_message(self): 1823 """ return the rule's error message """ 1824 return _("Birth equals marriage") 1825 1826class DeathEqualsMarriage(PersonRule): 1827 """ test if a person's death date is the same as their marriage date """ 1828 ID = 35 1829 SEVERITY = Rule.WARNING # it's possible 1830 def broken(self): 1831 """ return boolean indicating whether this rule is violated """ 1832 death_date = get_death_date(self.db, self.obj) 1833 death_ok = death_date > 0 if death_date is not None else False 1834 for fhandle in self.obj.get_family_handle_list(): 1835 family = self.db.get_family_from_handle(fhandle) 1836 marr_date = get_marriage_date(self.db, family) 1837 marr_ok = marr_date > 0 if marr_date is not None else False 1838 return marr_ok and death_ok and death_date == marr_date 1839 1840 def get_message(self): 1841 """ return the rule's error message """ 1842 return _("Death equals marriage") 1843 1844