Autoadding some entities for new created person Feature

Every genealogist has encountered repetitive scenarios when adding individuals to their database. Here are a few real-life examples:

  • When reviewing death records for a specific place, I often create a new person (assuming they are not already in my database). I always add two events: a birth with a calculated date and a death with an exact date and place, which usually remains consistent across entries.
  • When examining Cossack registers, I always add the attribute: Caste: Cossack.
  • When manually adding people from other databases, I consistently assign a special tag or add the same note to all of them.
  • There are countless such scenarios. I believe every researcher can recall similar patterns in their work.

What Could Be New, Interesting, and Useful in Gramps 5.3
I propose a new addon with no interface, featuring just a single settings page, like the one below.

Researchers can specify comma separated IDs, which events, notes, citations, media, tags, and other elements they would like to automatically add to newly created people. After working with a particular group or region, they can remove these IDs to stop adding items to new people or change them to new settings. Additionally, the settings could include checkboxes to automatically create events (birth, death, residence) with specific places.

1 Like

This feature is already working in Gramps. It does not have GUI-settings, but already works. I’ve modified: /usr/local/lib/python3.10/dist-packages/gramps/plugins/lib/libpersonview.py function add()
Here I added one row:

self.add_custom_entities(person, CONFIG_PATH)

The full function code:

    def add(self, *obj):
        """
        Add a new person to the database.
        """
        person = Person()
        # the editor requires a surname
        person.primary_name.add_surname(Surname())
        person.primary_name.set_primary_surname(0)
        
        self.add_custom_entities(person, CONFIG_PATH)

        try:
            EditPerson(self.dbstate, self.uistate, [], person)
        except WindowActiveError:
            pass

And below I also added 2 custom functions:

    def add_custom_entities(self, person, config_path):
        """
        Add custom entities to the person based on the configuration in the JSON file.
        """
        config = self.load_config(config_path)    
        if config is None:
            return

        # add notes
        for note_id in config.get("notes", []):
            note = self.dbstate.db.get_note_from_gramps_id(note_id)
            if note is not None:
                note_handle = note.get_handle()
                person.add_note(note_handle)    

        # add citations
        for citation_id in config.get("citations", []):
            citation = self.dbstate.db.get_citation_from_gramps_id(citation_id)
            if citation is not None:
                citation_handle = citation.get_handle()
                person.add_citation(citation_handle)    

        # add media
        for media_id in config.get("medias", []):
            media = self.dbstate.db.get_media_from_gramps_id(media_id)
            if media is not None:
                media_handle = media.get_handle()
                media_ref = MediaRef()
                media_ref.set_reference_handle(media_handle)
                person.add_media_reference(media_ref)    

        # add attributes
        for attr in config.get("attributes", []):
            attribute = Attribute()
            attribute.set_type(attr["key"])
            attribute.set_value(attr["value"])
            person.add_attribute(attribute)    

        # add tags
        for tag_name in config.get("tags", []):
            tag_handle = None
            for handle in self.dbstate.db.get_tag_handles():
                tag = self.dbstate.db.get_tag_from_handle(handle)
                if tag.get_name() == tag_name:
                    tag_handle = handle
                    break
            if tag_handle:
                person.add_tag(tag_handle)    

        # add events
        with DbTxn("Add events", self.dbstate.db) as trans:
            for event_config in config.get("events", []):
                event = Event()
                event_type_str = event_config["type"].upper()
                event_type = getattr(EventType, event_type_str, EventType.CUSTOM)
                event.set_type((event_type, event_type_str.capitalize()))
                if "place" in event_config:
                    place_handle = self.dbstate.db.get_place_from_gramps_id(event_config["place"]).get_handle()
                    if place_handle:
                        event.set_place_handle(place_handle)
                event_handle = self.dbstate.db.add_event(event, trans)
                
                event_ref = EventRef()
                event_ref.set_reference_handle(event_handle)
                person.add_event_ref(event_ref)
    
    @staticmethod
    def load_config(config_path=CONFIG_PATH):
        """
        Load configuration from a JSON file. If the JSON is malformed or cannot be loaded,
        log the error and return None.
        """
        try:
            with open(config_path, 'r') as file:
                return json.load(file)
        except (json.JSONDecodeError, FileNotFoundError) as e:
            print(f"Error loading config file {config_path}: {e}")
            return None

Also I’ve added several imports. The full imports list and my own constant:

from gramps.gen.lib import Person, Surname, Event, EventType
from gramps.gui.views.listview import ListView, TEXT, MARKUP, ICON
from gramps.gui.uimanager import ActionGroup
from gramps.gen.display.name import displayer as name_displayer
from gramps.gui.dialog import ErrorDialog
from gramps.gen.errors import WindowActiveError
from gramps.gui.views.bookmarks import PersonBookmarks
from gramps.gen.config import config
from gramps.gui.ddtargets import DdTargets
from gramps.gui.editors import EditPerson
from gramps.gui.filters.sidebar import PersonSidebarFilter
from gramps.gui.merge import MergePerson
from gramps.gen.plug import CATEGORY_QR_PERSON
from gramps.gen.lib.mediaref import MediaRef
from gramps.gen.lib import Attribute
from gramps.gen.lib.eventref import EventRef
from gramps.gen.db import DbTxn
import json

CONFIG_PATH = "/home/yurii/.local/share/gramps/gramps52/config.json"

Configuration file example:

{
    "notes": ["N10448","N10448"],
    "citations": ["C1234","C1234"],
    "medias": ["O0020"],
    "attributes": [
        {"key": "Caste", "value": "Farmer"}
    ],
    "tags": ["test tag"],
    "events": [
        {"type": "Birth", "place": "P0373"},
        {"type": "Death", "place": "P0373"},
        {"type": "Residence", "place": "P0373"}
    ]
}

Final result:

1 Like

I think this feature is simple in implementation and very useful. It works good for me, but a lot of users can not modify Gramps scripts to change config, add functions, can not work with json. It would be great if anybody will create addon with GUI-settings.

Oh, I see it works only on the “People page”.
Other places like “Grouped people”, adding from the main menu ignore this feature. Looks like Gramps code has duplicated add() methods in multiple places.

One more place where I added the same code fragments is:

sudo edit /usr/local/lib/python3.10/dist-packages/gramps/gui/editors/editfamily.py

In methods:

def add_mother_clicked(self, obj):
def add_father_clicked(self, obj):

I’ve made a big code updade. Now I can automatically create for a new person:

  • attach existing notes
  • attach existing citations
  • attach existing objects
  • attach existing attributes
  • attach existing tags
  • attach existing events
  • create and attach new events with dates, places, citations

It works as expected. It really helps in fast adding Census events for new people.
And also it was my dream:

  • auto-create empty Birth and Death events for people who is dead
  • auto-create empty Birth event for people who is not dead
from gramps.gen.lib import Person, Event, EventType
from gramps.gen.config import config
from gramps.gen.lib.mediaref import MediaRef
from gramps.gen.lib import Attribute
from gramps.gen.lib.eventref import EventRef
from gramps.gen.db import DbTxn
from gramps.gen.lib.date import Date
import json

CONFIG_PATH = "/home/yurii/.local/share/gramps/gramps52/config.json"

    # An existing function which adding a new person
    # There are multiple similar functions in Gramps code in different places
    def add(self, *obj):
        ...
        person = Person()
        ...

        # add this one row right before EditPerson
        self.add_custom_entities(person, CONFIG_PATH)

        try:
            EditPerson(self.dbstate, self.uistate, [], person)
        except WindowActiveError:
            pass

    def add_custom_entities(self, person, config_path):
        """
        Add custom entities to the person based on the configuration in the JSON file.
        """
        config = self.load_config(config_path)    
        if config is None:
            print(f"Exiting method: Configuration file at {config_path} is invalid.")
            return    

        # Adding notes to the person
        for note_id in config.get("notes", []):
            note = self.dbstate.db.get_note_from_gramps_id(note_id)
            if note is not None:
                note_handle = note.get_handle()
                person.add_note(note_handle)        

        # Adding citations to the person
        for citation_id in config.get("citations", []):
            citation = self.dbstate.db.get_citation_from_gramps_id(citation_id)
            if citation is not None:
                citation_handle = citation.get_handle()
                person.add_citation(citation_handle)        

        # Adding media to the person
        for media_id in config.get("medias", []):
            media = self.dbstate.db.get_media_from_gramps_id(media_id)
            if media is not None:
                media_handle = media.get_handle()
                media_ref = MediaRef()
                media_ref.set_reference_handle(media_handle)
                person.add_media_reference(media_ref)        

        # Adding attributes to the person
        for attr in config.get("attributes", []):
            attribute = Attribute()
            attribute.set_type(attr["key"])
            attribute.set_value(attr["value"])
            person.add_attribute(attribute)        

        # Adding tags to the person
        for tag_name in config.get("tags", []):
            tag_handle = None
            for handle in self.dbstate.db.get_tag_handles():
                tag = self.dbstate.db.get_tag_from_handle(handle)
                if tag.get_name() == tag_name:
                    tag_handle = handle
                    break
            if tag_handle:
                person.add_tag(tag_handle)        

        # Adding events to the person
        with DbTxn("Add events", self.dbstate.db) as trans:
            for event_config in config.get("events", []):
                if "gramps_id" in event_config:
                    event = self.dbstate.db.get_event_from_gramps_id(event_config["gramps_id"])
                    if event is None:
                        print(f"Event with Gramps ID {event_config['gramps_id']} not found.")
                        continue
                else:
                    event = Event()

                    # Set event type
                    event_type_str = event_config["type"].upper()
                    event_type = getattr(EventType, event_type_str, EventType.CUSTOM)
                    event.set_type((event_type, event_type_str.capitalize()))    

                    # Set event place (optional)
                    if "place" in event_config:
                        place_handle = self.dbstate.db.get_place_from_gramps_id(event_config["place"]).get_handle()
                        if place_handle:
                            event.set_place_handle(place_handle)
                
                    # Set event date (optional)
                    if "date" in event_config and event_config["date"]:
                        event_date = Date()

                        quality = getattr(Date, event_config["date"]["quality"], Date.QUAL_NONE)
                        modifier = getattr(Date, event_config["date"]["modifier"], Date.MOD_NONE)
                        value = tuple(event_config["date"]["value"])

                        event_date.set(quality=quality, modifier=modifier, calendar=None, value=value, text=None, newyear=0)
                        event.set_date_object(event_date)
                
                    # Add citations to the event (optional)
                    for citation_id in event_config.get("citations", []):
                        citation = self.dbstate.db.get_citation_from_gramps_id(citation_id)
                        if citation is not None:
                            event.add_citation(citation.get_handle())    

                # Add the event to the person
                event_handle = self.dbstate.db.add_event(event, trans)
                event_ref = EventRef()
                event_ref.set_reference_handle(event_handle)
                person.add_event_ref(event_ref)

    @staticmethod
    def load_config(config_path=CONFIG_PATH):
        """
        Load configuration from a JSON file. If the JSON is malformed or cannot be loaded,
        log the error and return None.
        """
        try:
            with open(config_path, 'r') as file:
                return json.load(file)
        except (json.JSONDecodeError, FileNotFoundError) as e:
            print(f"Error loading config file {config_path}: {e}")
            return None

JSON config example:

{
    "notes": ["N0001"],
    "citations": ["C4574"],
    "medias": ["O2698"],
    "attributes": [{"key": "Caste", "value": "Imperator"}],
    "tags": ["Pr(direction)","Rs(direction)"],
    "events": [
        {
            "type": "Birth",
            "date": {
                "quality": "QUAL_CALCULATED",
                "modifier": "MOD_ABOUT",
                "value": [0, 0, 2222, false]
            },
            "citations": ["C4575"]
        },
        {"gramps_id": "E0437"},
        {
            "type": "Death",
            "date": {
                "quality": "QUAL_ESTIMATED",
                "modifier": "MOD_RANGE",
                "value": [0, 0, 1851, false, 0, 0, 2222, false]
            },
            "citations": ["C4575"]
        },
        {
            "type": "Residence", 
            "place": "P0090", 
            "date": {
                "quality": "QUAL_NONE",
                "modifier": "MOD_NONE",
                "value": [0, 0, 1851, false]
            },
            "citations": ["C4575"]
        }
    ]
}

I only dont like in my script dates format like this:

"date": {
    "quality": "QUAL_ESTIMATED",
    "modifier": "MOD_RANGE",
    "value": [0, 0, 1851, false, 0, 0, 2222, false]
},

I would like use something like this:

“estimated between 1851 and 2222”

. I tried use set_as_text(), set_text_value(), set() functions of the Date class, but these functions dont parse such string “estimated between 1851 and 2222” to recognize it as date object. How can I do this?