# -*- coding: utf-8 -*- # # VerifyHTML - a Gramps tool # # Runs the built-in "Verify the Data" checks and saves the result as a # sortable HTML report in which each item can be ticked off once reviewed. # # Design principle # ---------------- # This tool does NOT copy the checks. It imports the class from the built-in # verify module and calls its run_the_tool() through a light "shim". The # report therefore follows along automatically whenever Gramps improves or # adds checks, and none of Gramps' own files are touched. # # The thresholds (maximum age, young father, ...) are read from the settings # you last saved in the built-in "Verify the Data" tool. Those settings are # only stored when you actually RUN that tool (it saves the options at the # end of a run), so run "Verify the Data" once with your preferred values # before running this report. # # --------------------------------------------------------------------------- import os import json import datetime import webbrowser from gi.repository import Gtk # --- Translation ------------------------------------------------------------ # English is the source language. A bundled locale//LC_MESSAGES/addon.mo # (e.g. Danish) is picked up automatically via get_addon_translator(). from gramps.gen.const import GRAMPS_LOCALE as glocale try: _trans = glocale.get_addon_translator(__file__) except (ValueError, AttributeError): _trans = glocale.translation _ = _trans.gettext # --- Gramps base classes ---------------------------------------------------- from gramps.gui.plug import tool from gramps.gui.dialog import OkDialog, ErrorDialog # --- Reuse of the built-in Verify tool -------------------------------------- # These names have been stable across Gramps 5.x and 6.x. from gramps.plugins.tool.verify import Verify, VerifyOptions, Rule # Default thresholds - only used as a fallback if your own saved settings # cannot be read (for example on a brand-new family tree). _DEFAULTS = { "oldage": 90, "hwdif": 30, "cspace": 8, "cbspan": 25, "yngmar": 17, "oldmar": 50, "oldmom": 48, "yngmom": 17, "yngdad": 18, "olddad": 65, "wedder": 3, "mxchildmom": 12, "mxchilddad": 15, "lngwdw": 30, "oldunm": 99, "estimate_age": 0, "invdate": 1, } # =========================================================================== # # Tool options (we keep none of our own - we borrow Verify's saved ones) # # =========================================================================== class VerifyHTMLOptions(tool.ToolOptions): """Empty options. Thresholds come from the built-in Verify tool.""" def __init__(self, name, person_id=None): tool.ToolOptions.__init__(self, name, person_id) self.options_dict = {} self.options_help = {} # =========================================================================== # # The tool itself # # =========================================================================== class VerifyHTMLTool(tool.Tool): """Run the built-in data checks and save them as a sortable HTML report.""" def __init__(self, dbstate, user, options_class, name, callback=None): tool.Tool.__init__(self, dbstate, options_class, name) self.dbstate = dbstate self.user = user self.uistate = getattr(user, "uistate", None) self.db = dbstate.db parent = self.uistate.window if self.uistate else None # 1) Read the thresholds you last saved in "Verify the Data" options_dict = self._load_saved_thresholds() # 2) Run the built-in checks (headless, no window) try: results = self._run_checks(options_dict) except Exception as err: # pragma: no cover - defensive ErrorDialog( _("Could not run the verification"), str(err), parent=parent, ) return # 3) Build the HTML report html_text = self._build_html(results) # 4) Save via a file dialog and open in the browser self._save_and_open(html_text, len(results), parent) # ----------------------------------------------------------------- step 1 def _load_saved_thresholds(self): """Read the settings saved by the built-in Verify tool.""" options_dict = dict(_DEFAULTS) try: saved = VerifyOptions("verify") saved.load_previous_values() source = getattr(saved, "options_dict", {}) or {} for key in _DEFAULTS: if key in source: options_dict[key] = source[key] except Exception: pass # fall back to _DEFAULTS return options_dict # ----------------------------------------------------------------- step 2 def _run_checks(self, options_dict): """ Run the verify module's own run_the_tool() headlessly. We create a Verify object WITHOUT calling its __init__ (which would otherwise open the dialog) and give it just the attributes that run_the_tool() touches. All rules and the whole traversal therefore come from Gramps itself - we merely collect the results. """ runner = Verify.__new__(Verify) runner.db = self.db runner.v_r = None class _Shim: pass handler = _Shim() handler.options_dict = options_dict opts = _Shim() opts.handler = handler runner.options = opts collected = [] runner.add_results = collected.append # collects (7-tuple) runner.set_total = lambda total: None # no progress bar runner.update = lambda *a, **k: None runner.run_the_tool(cli=True) # cli=True -> no GUI call return collected # ----------------------------------------------------------------- step 3 def _build_html(self, results): """Build the rows and insert them into the HTML template.""" rows = [] for item in results: # report_itself() -> (msg, gramps_id, name, type, rule_id, severity, handle) msg, gid, name, the_type, rule_id, severity, _handle = item sev = "error" if severity == Rule.ERROR else "warning" if the_type == "Person": obj = _("Person") elif the_type == "Family": obj = _("Family") else: obj = str(the_type) rows.append({ "sev": sev, "msg": msg or "", "obj": obj, "gid": gid or "", "name": name or "", "key": "%s|%s" % (gid, rule_id), }) n_err = sum(1 for r in rows if r["sev"] == "error") n_warn = len(rows) - n_err tree_name = self.db.get_dbname() or _("Untitled") stamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M") lang = (getattr(glocale, "lang", None) or "en")[:2] # All user-visible strings, routed through _() so a Danish .mo # translates the whole report. labels = { "doc_title": _("Data verification"), "h1": _("Data verification"), "tree_label": _("Family tree"), "generated": _("generated"), "total": _("items in total"), "errors": _("errors"), "warnings": _("warnings"), "reviewed": _("reviewed"), "f_all": _("All"), "f_err": _("Errors only"), "f_warn": _("Warnings only"), "search": _("Search name, ID or check\u2026"), "hide": _("Hide reviewed"), "reset": _("Reset check marks"), "savecopy": _("Save a copy"), "c_severity": _("Severity"), "c_check": _("Check"), "c_object": _("Object"), "c_id": _("ID"), "c_name": _("Name"), "chip_error": _("Error"), "chip_warning": _("Warning"), "empty": _("No items match the filter."), "confirm_reset": _("Remove all check marks for this family tree?"), "footer": _( "Check marks are stored in your browser and are normally kept " "between visits (each item has a stable key, so they survive " "even if the report is regenerated). To be completely sure of " "keeping your progress - or to move it to another device - click " "\u201cSave a copy\u201d: the downloaded file has your current " "check marks baked in. When the file is opened locally in Chrome, " "browser storage is not always kept between sessions, so saving a " "copy is the safe option; served over http or opened in Firefox " "it persists on its own." ), } data_json = json.dumps(rows, ensure_ascii=False).replace("", ">")) def _js_string(text): """Safe JS string body (without the surrounding quotes).""" return (json.dumps(str(text), ensure_ascii=False)[1:-1] .replace(" __L_DOCTITLE__ - __TREE__

__L_H1__

__L_TREE__: __TREE__  ·  __L_GENERATED__ __STAMP__
__TOTAL__ __L_TOTAL__ __ERR__ __L_ERRORS__ __WARN__ __L_WARNINGS__ 0 __L_REVIEWED__
__L_CSEV__ __L_CCHECK__ __L_COBJ__ __L_CID__ __L_CNAME__
__L_FOOTER__
"""