Open main menu

Gramps β

Changes

Report-writing tutorial

363 bytes removed, 10:38, 2 December 2025
Defining the Report class: A code description is aligned with the new code
* The most common surname
{{man tip|From Gramps version 3.2, there is also|A [[Simple Access API|simple access]] database [https://www.gramps-project.org/docs/ gen/gen_db.html API] available, with accompanying [[Quick Views]], [[Gramplets_development|Gramplets]] and [[Addons development|Addons]].}}
==Overview==
===report.gpr.py===
;Registration statement : This initializes the report by a single call to the <tt>[https://gramps-project.org/docs/gen/gen_plug.html#module-gramps.gen.plug._pluginreg .register register()]</tt> function. It is trivial, but without it your report will not become available to Gramps, even if it is otherwise perfectly written.
A report can potentially be generated as a standalone report, as a Gramps Book item, and as a command line report. The registration determines which modes are enabled for a given report. The report class does not have to know anything about the mode. The options class is there to provide options interface for all available modes.
===report.py===
;Report class : This is the code that takes data from the Gramps database and organizes it into the document structure. This structure can later be printed, viewed, or written into a file in a variety of formats. This class uses the [https://gramps-project.org/docs/gen/gen_plug.html#module-gramps.gen.plug._docgenplugin docgen] interface to abstract away the output format details.
;Options class : This is the code that provides means to obtain options necessary for the report using a variety of available mechanisms.
The [[Report API]] article provides more details about the interfaces.
The developer [https://gramps-project.org/docs/gen/gen_plug.html#module-gramps.gen.plug._docgenplugin docgen] api documentation provides a detailed specification of the interfaces.
Gramps attempts to abstract the output document format away from the report. By coding to the [https://gramps-project.org/docs/gen/gen_plug.html#module-gramps.gen.plug._docgenplugin docgen] class, the report can generate its output in the format desired by the end user. The document passed to the report (<tt>self.doc</tt>) could represent an HTML, OpenDocument, PDF or any of the other formats supported by the user. The report does not have to concern itself with the output format details, since all details are handled by the document object.
A document is composed of paragraphs, tables, and graphics objects. Tables and graphics objects will not be covered in this tutorial.
Paragraph and font styles are defined in the <tt>make_default_style()</tt> function of the options class. The paragraphs are grouped into a <tt>StyleSheet</tt>, which the <tt>make_default_style()</tt> function defines. For the example report (<tt>DbSummary</tt>), the paragraph styles are defined as below:
{{stub}}<!-- need to check code is correct for Gramps 5.1.x -->
compare to <code>gramps\plugins\textreport\summary.py</code> starting at [https://github.com/gramps-project/gramps/blob/master/gramps/plugins/textreport/summary.py#L297 line 297]
<pre>
def make_default_style(self, default_style):
==Defining the classes==
===Report class===
The user's report class should inherit from the [https://www.gramps-project.org/docs/gen/gen_plug.html#gramps.gen.plug.report._reportbase.Report Report] class contained within the Report <code>gramps.gen.plug.report</code> module. The constructor should take three arguments (besides class instance itself, usually denoted by 'self' name):
* Gramps database instance
* options class instance
The first is the database to work with. The second is the instance of the options class defined in the same report, see next section. The third is an instance of the User class, used for interaction with the user. Here's an example of a report class definition:
<pre>
from ReportBase gramps.gen.plug.report import Report, ReportUtils, ReportOptions
class ReportClassName(Report):
</pre>
The Report class's constructor will initialize several variables for the user based off the passed values. They are:
;self.doc : The opened document instance ready for output. This is of one of the type [https://gramps-project.org/docs/gen/gen_plug.html#module-gramps.gen.plug._docgenplugin docgen.basedoc DocGen's Generator]types, and is '''not''' a normal file object.;self.database : The [httphttps://www.gramps-project.org/docs/gen/gen_libgen_db.html#module-gramps.gen.lib GrampsDbBasedb.base.DbReadBase DbReadBase] database object.;self.options_class : The [httphttps://www.gramps-project.org/docs/gen/gen_plug.html#module-gramps.gen.plug._docgenplugin report._options.ReportOptions ReportOptions] class passed to the report.
You'll probably need a start-person for which to write the report. This person should be obtained from the <tt>options_class</tt> object through the [https://gramps-project.org/docs/gen/gen_plug.html#gramps.gen.plug.menu._person.PersonOption PersonOption] class which will default to the active person in the database. Anything else the report class needs in order to produce the report should be obtained from the <tt>options_class</tt> object. For example, you may need to include the additional code in the report class constructor to obtain any options you defined for the report.
Report class '''must''' provide a <tt>write_report()</tt> method. This method should dump the report's contents into the already opened document instance.
===Options class===
* In theory Options class should derive from [httphttps://packageswww.pythongramps-project.org/Grampsdocs/gen/gen_plug.html#module-gramps.gen.plug.report._options .ReportOptions ReportOptions] class. Usually But in pratice for a common report the <tt>[https://www.gramps-project.org/docs/gen/gen_plug.html#gramps.gen.plug.report._options.MenuReportOptions MenuReportOptions</tt> ] class is used instead, which will abstract most of the lower-level widget handling. In this tutorial we'll assume that Options class should be is derived from[https://www.gramps-project. org/docs/gen/gen_plug.html#gramps.gen.plug.report._options.MenuReportOptions MenuReportOptions]. ====Defining parameters of the report ==== To define options for your report, you need to override <ttcode>MenuReportOptions.add_menu_options()</ttcode> method. Then [https://www.gramps-project.org/docs/gen/gen_plug.html#gramps.gen.plug.report._options.MenuReportOptions MenuReportOptions] class will abstract most present the user with a standard menu interface for running the report. You may generate a menu using one or more of the lowerclasses available in [https://www.gramps-project.org/docs/gen/gen_plug.html#module-gramps.gen.plug.menu._menu <code>gramps.gen.plug.menu</code>] package such as [https://www.gramps-project.org/docs/gen/gen_plug.html#gramps.gen.plug.menu._booleanlist.BooleanListOption BooleanListOption] class. Also you may use standard Gramps options from [https://github.com/gramps-level widget handling belowproject/gramps/blob/dba1e4e5555694194836d2934542e30c0f75d1b6/gramps/gen/plug/report/stdoptions.py <code>gramps.gen.plug.report.stdoptions</code>] module.For example:
====Using ReportOptions====
<pre>
class OptionsClassNamedef add_menu_options(ReportOptionsself, menu): def __init__ """ Add options to the menu for this report. """ category_name = _("Report Options") what_types = BooleanListOption(_('Report for')) what_types.add_button(_('Males'), True) what_types.add_button(_('Females'), True) what_types.add_button(_(self'Unknown gender'),nameTrue) what_types.add_button(_('Other'),person_id=NoneTrue): ReportOptionsmenu.__init__add_option(selfcategory_name, "what_types",namewhat_types)  stdoptions.add_localization_option(menu,person_idcategory_name)
</pre>
* It should set new options that are specific for In this example <code>category name</code> is used to generate tab "Report Options" on the reportparameter entry dialog.  Object <code>what_types</code> of class [https://www.gramps-project.org/docs/gen/gen_plug.html#gramps.gen.plug.menu._booleanlist.BooleanListOption BooleanListOption] is created that presents the user with a group of check boxes, by overriding one is created for each call to [https://gramps-project.org/docs/gen/gen_plug.html#gramps.gen.plug.menu._booleanlist.BooleanListOption.add_button what_types.add_button()]. Finally the object is added to the menu with [https://gramps-project.org/docs/gen/gen_plug.html#gramps.gen.plug.menu._menu.Menu.add_option menu.add_option()].  Function <ttcode>set_new_optionsstdoptions.add_localization_option()</ttcode> method which defines adds to the same <ttcode>options_dictmenu</ttcode> and a standard Gramps option for localizing the report into a different locale from the UI locale.  Then to access the selected values once the user runs the report, you make a call the menu object from within the report's <ttcode>options_help__init__()</ttcode> dictionariesmethod. For example, to access the "what_types" that are selected from the menu above you would add the following code
<pre>
def set_new_options__init__(self, database, options_class, user): # Options specific for this report Report.__init__(self.options_dict = {, database, options_class, user)  what_types_option = options_class.menu.get_option_by_name('my_first_optionwhat_types' : 0,) 'my_second_option' : '', } self.options_help what_types = { 'my_first_option' : what_types_option.get_selected("=num","Number of something",) [ "First value", "Second value" ], True), 'my_second_option' : self.set_locale("=str","Some necessary string for the report", options_class.menu.get_option_by_name("Whatever String You Wishtrans"), }</pre>* It should also enable the "semi-common" options that are used in this report, by overriding the <tt>enable_options</tt> method which defines <tt>enable_dict</tt> dictionary. The semi-commons are the options which Gramps knows about, but which are not necessarily present in all reports:<pre>def enable_optionsget_value(self): # Semi-common options that should be enabled for this report self.enable_dict = { 'filter' : 0, })
</pre>
All the common options are already taken care of by the core of Gramps.
* For any new options set up in the options class, there must be defined UI widgets to provide means of changing these options through the dialogs. Also, there must be defined methods to extract values of these options from the widgets and to set them into the class-variable dictionary:
<pre>
def add_user_options(self,dialog):
option_menu = gtk.OptionMenu()
self.the_menu = gtk.Menu()
for item_index in rangeIn the example, <code>what_types_option</code> instance is retrieved by the [https://gramps-project.org/docs/gen/gen_plug.html#gramps.gen.plug.menu._menu.Menu.get_option_by_name options_class.menu.get_option_by_name(10)] method. The string passed to the method must match the name you passed as the second argument to [https: item = _//gramps-project.org/docs/gen/gen_plug.html#gramps.gen.plug.menu._menu.Menu.add_option menu.add_option("Item numer %d") % item_index menuitem = gtk] when you created the menu.MenuItem( Then a list of the selected item) menuitemtitles is retrieved with [https://gramps-project.org/docs/gen/gen_plug.html#gramps.gen.plug.menu._booleanlist.BooleanListOption.get_selected what_types_option.showget_selected() ] and stored as a instance variable <code>self.the_menuwhat_types</code> for later use.append(menuitem)
option_menuThe standard Gramps option for localizing the report has name "trans".set_menuIts value is passed to [https://www.gramps-project.org/docs/gen/gen_plug.html#gramps.gen.plug.report._reportbase.Report.set_locale <code>Report.set_locale()</code>] method that creates <code>self.the_menu_() option_menu</code> method for the locale chosen by the report's user.set_historyIt is then used by <code>write_report()</code> (selfsee below).options_dict['my_first_option'])
dialog.add_option(_('My first option'),option_menu)====Defining user-adjustable paragraph styles ====
selfIf the report uses the user-adjustable paragraph styles the default definitions for the styles must be defined here by overriding method [https://www.the_string_entry = gtkgramps-project.Entry() if selforg/docs/gen/gen_plug.options_dict['my_second_option']: selfhtml#gramps.the_string_entrygen.set_text(selfplug.options_dict['my_second_option']) else: selfreport.the_string_entry_options.set_text(_("Empty string")) self.the_string_entryReportOptions.showmake_default_style make_default_style() dialog.add_option(_(], to form a 'My second optiondefault'),self.the_string_entry)style sheet:
def parse_user_options(self,dialog):
self.options_dict['my_second_option'] = unicode(self.the_string_entry.get_text())
self.options_dict['my_first_option'] = self.the_menu.get_history()
</pre>
* Finally, the default definitions for the user-adjustable paragraph styles must be defined here, to form a 'default' stylesheet:
<pre>
def make_default_style(self, default_style):
default_style.add_style("ABC-Name",p)
</pre>
 
====Using MenuReportOptions====
 
The <tt>MenuReportOptions</tt> can be used in place of <tt>ReportOptions</tt> to present the user with a standard interface for running the report. Instead of parsing options, you generate a menu using one or more of the classes available in <tt>gen.plug.menu</tt>. All these are initialzed in the <tt>add_menu_options</tt> function (which is a required function when you inherit from <tt>MenuReportOptions</tt>). For example:
 
<pre>
def add_menu_options(self, menu):
"""
Add options to the menu for this report.
"""
category_name = _("Report Options")
what_types = BooleanListOption(_('Select From:'))
what_types.add_button(_('People'), True)
what_types.add_button(_('Families'), False)
what_types.add_button(_('Places'), False)
what_types.add_button(_('Events'), False)
menu.add_option(category_name, "what_types", what_types)
</pre>
 
In this example a <tt>BooleanListOption</tt> object is created that presents the user with a group of check boxes, one is created for each call to <tt>add_button</tt>. Finally the object is added to the menu with <tt>menu.add_option()</tt>. The category name is used to generate tabs on the report dialog.
 
Then to access the selected values once the user runs the report, you make a call the menu object from within the report's <tt>__init__</tt> function. For example, to access the "what_types" that are selected from the menu above you would add the following code:
 
<pre>
class ExampleReport(Report):
 
def __init__(self, database, options_class):
 
Report.__init__(self, database, options_class)
 
menu_option = options_class.menu.get_option_by_name('what_types')
self.what_types = menu_option.get_selected()
</pre>
 
In the example, the option class is retrieved by the <tt>get_option_by_name()</tt> function. The string must match the name you passed as the second argument to <tt>menu.add_option()</tt> when you created the menu. Then a list of the selected item titles is retrieved with <tt>get_selected()</tt> and stored as a class member for later use.
==Implementation==
===Defining the Report Options ReportOptions class=== In this example, no special options are required. This makes the options class very simple. All that is necessary is to define the default styles.<pre>class DbSummaryOptions(ReportOptions):
def __init__(selfIn this tutorial, the report has two options. The first, called <code>what_types</code>, allows a user to choose whether the counts of persons whose gender is Male, nameFemale, databaseUnknown or Other are shown in the report.The second is the standard Gramps option that allows the user to choose the locale for the report different from UI locale. It is added by calling <code>stdoptions.add_localization_option()</code> method. These two options are added in the overridden <code>add_menu_options():</code> method.
ReportOptionsTo define two unique styles for the report: DBS-Title and DBS-Normal the method <code>make_default_style()</code> is overridden too. Both styles are added by calling <code>default_style.__init__add_paragraph_style(self, name, database)</code> method.
def make_default_style(self, default_style):{{RWT_ReportOptions.py}} # Define the title paragraph, named So create now file 'DBS-Title', which uses a # 18 point, bold Sans Serif font with a paragraph that is centered  font = docgen.FontStyle() font.set_size(18) font.set_type_face(docgen.FONT_SANS_SERIF) font.set_bold(True)  para = docgen.ParagraphStyle() para.set_header_level(1) para.set_alignment(docgen.PARA_ALIGN_CENTER) para.set_font(font) para.set_description(_('The style used for the title of the pagereport.py'))  default_style.add_style('DBS-Title',para)  # Define and copy there the normal paragraph, named 'DBS-Normal', which uses a # 12 point, Serif fontabove code font = docgen.FontStyle() font.set_size(12) font.set_type_face(docgen.FONT_SERIF)  para = docgen.ParagraphStyle() para.set_font(font) para.set_description(_('The style used for normal text'))  default_style.add_style('DBS-Normal',para)</pre>
===Defining the Report class===
The actual implemention implementation of the <ttcode>DbSummaryDbSummaryReport</ttcode> report is rather simple. No additional work needs to be done to To initialize the class, so the parent <ttcode>Report.__init__()</ttcode> routine is called. All and then values of the work is done in options entered by the user are extracted from <ttcode>write_report()options_class</ttcode> function. This function uses a parameter which is of class <ttcode>GrampsCursorDbSummaryOptions</ttcode> to iterate through the map of . An instance variable <ttcode>Personself.what_types</ttcode> objects and gathers some simple statistics. The only thing is assigned a list of any complication is the determination names of check boxes selected by the most common surnameuser. A python dictionary is used to store the number of times each surname is usedThen <code>self. Each time a surname set_locale()</code> method is encountered, called with the value in of the dictionary is incremented. The results are then loaded into a list and sorted, allowing us locale to be used to find run the most common name by looking at the last entry in the list.<pre>class DbSummaryReport(Report):  def __init__(self, database, options_class):  Reportreport.__init__(self, database, options_class)
def write_reportAll the work is done in the <code>_count()</code> method. It uses a [https://www.gramps-project.org/docs/gen/gen_db.html#gramps.gen.db.generic.DbGeneric.iter_people <code>self.database.iter_people()</code>] to iterate through [https://www.gramps-project.org/docs/gen/gen_lib.html#module-gramps.gen.lib.person <code>Person</code>] objects and gathers some simple statistics. The only thing of any complication is the determination of the most common surnames. A python dictionary is used to store the number of times each surname is used. Each time a surname is encountered, the value in the dictionary is incremented. The results are then loaded into a list and sorted in the reverse order <code>slist.sort(reverse=True)</code>, allowing us to find the most common names by looking at the first entries in the list.
cursor = self.databaseFinally <code>write_report()</code> method outputs the data collected in accordance with the options selected by the report user.get_person_cursorNote that the strings being used in the report (such as "Report Options", "Males" etc)have already been used somewhere else in Gramps and have already been translated into various languages. Thus the content of our report as well as its parameters are already translated!
data = cursor.first()
males = 0 females = 0 total = 0 surname_map = {{RWT_Report.py}} while data: person = RelLib.Person() person.unserialize(data[1])  if person.get_gender() == RelLib.Person.MALE: males += 1 if person.get_gender() == RelLib.Person.FEMALE: females += 1 total += 1  surname = person.get_primary_name().get_surname()  if surname_map.has_key(surname): surname_map[surname] += 1 else: surname_map[surname] = 1  data = cursor.next() cursor.close()  slist = [] for key in surname_map.keys(): slist.append((surname_map[key],key)) slist.sort()  self.doc.start_paragraph("DBS-Title") self.doc.write_text(_("Database Summary")) self.doc.end_paragraph()  self.doc.start_paragraph(Append the above code to the 'DBS-Normal') self.doc.write_text(_('Number of males : %d') % males) self.doc.end_paragraph()  self.doc.start_paragraph('DBS-Normal') self.doc.write_text(_('Number of females : %d') % females) self.doc.end_paragraph()  selfreport.doc.start_paragraph(py'DBS-Normal') self.doc.write_text(_('Total people : %d') % total) self.doc.end_paragraph()  self.doc.start_paragraph('DBS-Normal') self.doc.write_text(_('Number of unique surnames : %d') % len(slist)) self.doc.end_paragraph()  self.doc.start_paragraph('DBS-Normal') self.doc.write_text(_('Most common surname : %s') % (slist[-1][1])) self.doc.end_paragraph()</pre>file
===Registering the report===
* Registration is set into a ''<name>.gpr.py'' file* The registration consists from a single call to [https://www.gramps-project.org/docs/gen/gen_plug.html#gramps.gen.plug._pluginreg.register register()] function* Registration should define internal name <code>id</code> of the report (preferably, single string with non-special ascii characters, usable for report identification from the command line and in the options storage, as well as for forming sane filename for storing its own styles). * It should also define the report's...** <code>category </code> (text/graphics/code)** translated translatable <code>name </code> (the one to display in menus)** modes that should be enabled for the report (standalone, book item, command line)* If the report requires an active person to run, then <code>require_active</code> should be set to <code>True</code>.** The <code>report_modes</code> argument is set to a list of zero or more of the following modes: *** REPORT_MODE_GUI (available for Gramps running in a window, graphical user interface)*** REPORT_MODE_BKI (available as a book report item)*** REPORT_MODE_CLI (available for the command line interface)** Finally, both report class <code>reportclass</code> and options class <code>optionsclass</code> names should be passed to registration.Descriptions of the other arguments of [https://www.gramps-project.org/docs/gen/gen_plug.html#gramps.gen.plug._pluginreg.register register()] function can be found [https://www.gramps-project.org/docs/gen/gen_plug.html#module-gramps.gen.plug._pluginreg here]. Here's the registration statement for our report:<pre>from gramps.gen.plug._pluginreg import *from gramps.gen.const import GRAMPS_LOCALE as glocale_ = glocale.translation.gettext
Here's the [[Quick_Views#Analysis:_registering_the_report|example registration]] statement:
<pre>
register(REPORT,
id = 'an unique idRWT_ID', name = _("Name of the pluginReport-writing tutorial"), description = _("Produces a ...simple database summary"), version = '1.0', gramps_target_version = '56.0.16', status = STABLE, fname = 'filenamereport.py', authors = ["John Doe"], authors_email = ["[email protected]"], category = CATEGORY_TEXT, require_active = False, reportclass = 'DbSummaryReport', optionclass = 'DbSummaryOptions', report_modes = [REPORT_MODE_GUI, REPORT_MODE_CLI], )
</pre>
There are arguments for the report class and options class. The <tt>report_modes</tt> argument is set to a list of zero or more of the following modes:
 
* REPORT_MODE_GUI (available for Gramps running in a window, graphical user interface)
* REPORT_MODE_BKI (available as a book report item)
* REPORT_MODE_CLI (available for the command line interface)
The list should only include those options which are valid for this Copy the above code into '''report.gpr.py''' file.
The rest of the options should be self-explanatory.
====Book ReportRemark regarding book report mode ====
To turn a report into one that will work as a book report, you may need add REPORT_MODE_BKI to the list of <code>report_modes</code> above. Also you have to add additional code to <code>write_report()</code> method of the Report class, similar to the followingcode below:
<pre>
</pre>
See an existing book report reports for more details. DO NOT copy the above code into '''report.py''' file since we do not intend to use our report in Book mode.
== Manually installing your report ==
Install in the plugins directory of your Gramps user directory. Go to ~/.local/share/gramps/gramps60/plugin directory (or similar for your version of Gramps) and create there RWT subdirectory. Then copy into RWT both '''report.py''' and '''report.gpr.py''' files.
== Example output ==
Start Gramps and your tutorial report will be available from the reports menu as  [[File:Rwt_menu....png]] Run the report and this is what the output looks like
Run the report and this is what the output looks like ...[[File:Example_RWT_ID.jpg]]
==See also==
* Discourse forum discussion: [https://gramps.discourse.group/t/sample-report-for-new-developers/3046 Sample Report for new developers]
[[Category:Addons|*]]
[[Category:Developers/General]]
[[Category:Developers/Tutorials]]
[[Category:Plugins]]
[[Category:Reports]]
64
edits