Difference between revisions of "Учебник по написанию отчетов"

From Gramps
(Интерфейс создания документа: Translated into Russian)
(Класс Report: Translated into Russian)
Line 83: Line 83:
  
 
==Определение классов==
 
==Определение классов==
===Класс Report===
+
===Класс Report ===
The user's report class should inherit from the Report class contained within the Report module. The constructor should take three arguments (besides class instance itself, usually denoted by 'self' name):
+
Пользовательский класс отчета должен наследовать от класса Report находящемся в модуле <code>gramps.gen.plug.report</code>. Конструктор должен принимать три аргумента (не считая ссылки на текущий экземпляр класса, обычно называемый 'self'):
* Gramps database instance
+
 
* options class instance
+
* экземпляр базы данных Gramps  
* user 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:
+
* экземпляр класса пользователя
 +
 +
Первый аргумент - это база данных для работы. Второй аргумент - это экземпляр класса параметров отчета, определенный вместе с самим отчетом, как описано в следующем разделе. Третий аргумент - это экземпляр класса User, используемый для интерактивного взаимодействия с пользователем. Ниже приведен пример определения класса отчета:
 +
 
 
<pre>
 
<pre>
from ReportBase import Report, ReportUtils, ReportOptions
+
from gramps.gen.plug.report import Report
  
 
class ReportClassName(Report):
 
class ReportClassName(Report):
Line 96: Line 99:
 
         Report.__init__(self, database, options_class, user)
 
         Report.__init__(self, database, options_class, user)
 
</pre>
 
</pre>
The Report class's constructor will initialize several variables for the user based off the passed values. They are:
+
Конструктор класса Report на основе переданных ему параметров инициализирует для пользователя несколько переменных. Это:
;self.doc : The opened document instance ready for output. This is of the type [https://gramps-project.org/docs/gen/gen_plug.html#module-gramps.gen.plug._docgenplugin docgen], and is '''not''' a normal file object.
+
;self.doc : Экземпляр открытого документа, готового к выводу. Его тип - это один из типов [https://gramps-project.org/docs/gen/gen_plug.html#module-gramps.gen.plug.docgen.basedoc Генераторов DocGen], и '''НЕ ЯВЛЯЕТСЯ''' типом обычного файла.
;self.database : The [https://www.gramps-project.org/docs/gen/gen_db.html#module-gramps.gen.db.base GrampsDbBase] database object.
+
;self.database : Объект класса [https://gramps-project.org/docs/gen/gen_db.html#gramps.gen.db.base.DbReadBase DbReadBase].
;self.options_class : The [https://www.gramps-project.org/docs/gen/gen_plug.html#gramps.gen.plug.report._options.ReportOptions ReportOptions] class passed to the report.
+
;self.options_class : Объект класса [https://www.gramps-project.org/docs/gen/gen_plug.html#gramps.gen.plug.report._options.ReportOptions ReportOptions], переданный отчету.
  
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 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.
+
Возможно Вам для начала потребуется знать человека, о котором создается отчет. Этого человека можно получить из объекта <tt>options_class</tt> используя класс [https://gramps-project.org/docs/gen/gen_plug.html#gramps.gen.plug.menu._person.PersonOption PersonOption], который по умолчанию вернет активное лицо в базе данных. Вообще говоря всё, что классу Report требуется для создания отчёта, должно быть получено из объекта <tt>options_class</tt>. Например, Вы можете добавить код в конструктор класса 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.
+
Пользовательский класс отчета  ''должен''' содержать метод <tt>write_report()</tt>. Этот метод выводит содержимое отчета в экземпляр открытого документа.  
 
<pre>
 
<pre>
 
def write_report(self):
 
def write_report(self):
Line 110: Line 113:
 
     self.doc.end_paragraph()
 
     self.doc.end_paragraph()
 
</pre>
 
</pre>
The rest of the report class is pretty much up to the report writer. Depending on the goals and the scope of the report, there can be any amount of code involved. When the user generates the report in any mode, the class constructor will be run, and then the <tt>write_report()</tt> method will be called. So if you wrote that beautiful method listing something really important, make sure it is eventually called from within the <tt>write_report()</tt>. Otherwise nobody will see it unless looking at the code.
+
Все остальное в пользовательском классе отчета - на усмотрение разработчика. Он может содержать произвольное количество кода, зависящее только от целей и охвата отчёта. Когда конечный пользователь будет запускать отчёт, в независимости от режима запуска сначала будет выполнен конструктор, а затем будет вызван метод <tt>write_report()</tt>. Так что если Вы написали прекрасный метод выводящий что-то действительно важное, удостоверьтесь, что он вызывается из метода <tt>write_report()</tt>. В противном случае, никто не увидит ваш метод, если только не посмотрит код класса.
  
 
===Класс Options===
 
===Класс Options===

Revision as of 10:26, 26 November 2025

Этот учебник содержит небольшое упражнение по написанию простейшего отчета на основе инфраструктуры Gramps для создания отчетов. Упражнение включает в себя работу с параметрами отчета, создание документа и вывод отчета.

В результате выполнения упражнения будет написан отчет, содержащий общую информацию о базе данных. Он будет включать в себя:

  • Количество людей в базе данных
  • Количество мужчин и женщин
  • Количество уникальных фамилий
  • Фамилия, которая встречается чаще всего
Tango-Dialog-information.png
Начиная с Gramps версии 3.2, доступны так же

руководства по использованию простого программного интерфейса базы данных API, а так же созданию отчётов быстрого просмотра, разработке грамплетов и дополнений.


Обзор

Перед тем, как углубляться в детали, полезно отметить, что отчет должен состоять из двух основных частей. Как объяснено на странице Разработка дополнений, исходный код распределяется по двум различным файлам:

  1. Файл регистрации дополнения Gramps (*.gpr.py), как например: report.gpr.py
  2. и основной файл исходного кода (*.py), например: report.py

report.gpr.py

Код регистрации 
Этот код, инициализирующий отчет, состоит из единственного вызова функции register(). Это простое действие, без которого отчет, как бы хорош он не был, не будет доступен в Gramps.

Любой отчет Gramps может быть выполнен в одном из трех режимов: как автономный отчет, как элемент Книги Gramps, а так же может быть запущен из командной строки. Код регистрации сообщает Gramps, в каких из этих режимов отчет может быть выполнен. Класс Report не должен ничего знать о режиме выполнения: класс Options предназначен для реализации соответствующего интерфейса задания параметров отчета для всех режимов запуска.

report.py

Код класса Report
Содержит код, который получает данные из базы данных Gramps и организует их в структуру документа. Эта структура может быть затем напечатана, просмотрена или записана в файл в различных форматах. Этот класс использует интерфейс docgen чтобы абстрагироваться от формата вывода.
Код класса Options
Содержит код, дающий возможность задать необходимые для отчёта параметры, используя множество различных механизмов передачи параметров.

Интерфейс создания структуры документа

Статья Cоздание структуры документа содержит обзор всех интерфейсов модуля docgen, доступных для создания структуры документа.

Статья Интерфейс программирования отчетов предоставляет более детальную информацию об интерфейсах.

Документация API(интерфейса программирования приложений) docgen, предназначенная для разработчиков, содержит подробную спецификацию интерфейсов.

Gramps пытается абстрагировать формат вывода от содержимого отчета. Если отчёт запрограммирован на основе классов docgen, вывод содержимого отчёта может осуществляться в том формате, который выберет пользователь.

Объект Документ (self.doc), переданный в отчёт, может выводить содержимое отчёта в формате HTML, OpenDocument, PDF или любом другом из поддерживаемых форматов, по выбору пользователя. Самому отчёту нет необходимости заботиться о формате вывода, т.к. все связанные с этим задачи выполняются объектом Документ.

Структура документа может быть составлена из абзацев, таблиц и графических объектов. Таблицы и графические объекты не будут рассмотрены в этом учебнике.

Каждый отчёт задает своё множество стилей для абзацев и шрифтов, вместе с их настройками по умолчанию. Пользователь может заменить определение каждого стиля, что, в свою очередь позволяет пользователю настроить отчёт под свои требования. Каждый стиль абзаца должен иметь уникальное имя для того, чтобы не возникало коллизий при использовании отчёта в формате Книги. Рекомендуется к имени каждого стиля для абзацев добавлять трех буквенный префикс, уникальный для отчёта.

Cтили абзацев и шрифтов определяются в методе make_default_style() класса Options. Затем стили абзацев объединяются в стиль "по умолчанию", который создается методом make_default_style(). Например, для отчета, который будет нами создан в этом учебнике (DbSummary), стили абзацев определены следующим образом:

def make_default_style(self, default_style):

    # Define the title paragraph, named '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 page.'))

    default_style.add_style('DBS-Title',para)

    # Define the normal paragraph, named 'DBS-Normal', which uses a
    # 12 point, Serif font.

    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)

Определение классов

Класс Report

Пользовательский класс отчета должен наследовать от класса Report находящемся в модуле gramps.gen.plug.report. Конструктор должен принимать три аргумента (не считая ссылки на текущий экземпляр класса, обычно называемый 'self'):

  • экземпляр базы данных Gramps
  • экземпляр класса параметров
  • экземпляр класса пользователя

Первый аргумент - это база данных для работы. Второй аргумент - это экземпляр класса параметров отчета, определенный вместе с самим отчетом, как описано в следующем разделе. Третий аргумент - это экземпляр класса User, используемый для интерактивного взаимодействия с пользователем. Ниже приведен пример определения класса отчета:

from gramps.gen.plug.report import Report

class ReportClassName(Report):
    def __init__(self, database, options_class, user):
        Report.__init__(self, database, options_class, user)

Конструктор класса Report на основе переданных ему параметров инициализирует для пользователя несколько переменных. Это:

self.doc 
Экземпляр открытого документа, готового к выводу. Его тип - это один из типов Генераторов DocGen, и НЕ ЯВЛЯЕТСЯ типом обычного файла.
self.database 
Объект класса DbReadBase.
self.options_class 
Объект класса ReportOptions, переданный отчету.

Возможно Вам для начала потребуется знать человека, о котором создается отчет. Этого человека можно получить из объекта options_class используя класс PersonOption, который по умолчанию вернет активное лицо в базе данных. Вообще говоря всё, что классу Report требуется для создания отчёта, должно быть получено из объекта options_class. Например, Вы можете добавить код в конструктор класса Report для получения параметров, которые Вы задали для отчета.

Пользовательский класс отчета должен' содержать метод write_report(). Этот метод выводит содержимое отчета в экземпляр открытого документа.

def write_report(self):
    self.doc.start_paragraph("ABC-Title")
    self.doc.write_text(_("Some text"))
    self.doc.end_paragraph()

Все остальное в пользовательском классе отчета - на усмотрение разработчика. Он может содержать произвольное количество кода, зависящее только от целей и охвата отчёта. Когда конечный пользователь будет запускать отчёт, в независимости от режима запуска сначала будет выполнен конструктор, а затем будет вызван метод write_report(). Так что если Вы написали прекрасный метод выводящий что-то действительно важное, удостоверьтесь, что он вызывается из метода write_report(). В противном случае, никто не увидит ваш метод, если только не посмотрит код класса.

Класс Options

  • Options class should derive from ReportOptions class. Usually for a common report the MenuReportOptions class should be derived from. MenuReportOptions will abstract most of the lower-level widget handling below.

Использование класса ReportOptions

class OptionsClassName(ReportOptions):
    def __init__(self,name,person_id=None):
        ReportOptions.__init__(self,name,person_id)
  • It should set new options that are specific for this report, by overriding the set_new_options() method which defines options_dict and options_help dictionaries:
def set_new_options(self):
    # Options specific for this report
    self.options_dict = {
        'my_first_option'    : 0,
        'my_second_option'  : '',
    }
    self.options_help = {
        'my_first_option'    : ("=num","Number of something",
                               [ "First value", "Second value" ],
                               True),
        'my_second_option'  : ("=str","Some necessary string for the report",
                               "Whatever String You Wish"),
  }
  • It should also enable the "semi-common" options that are used in this report, by overriding the enable_options method which defines enable_dict dictionary. The semi-commons are the options which Gramps knows about, but which are not necessarily present in all reports:
def enable_options(self):
    # Semi-common options that should be enabled for this report
    self.enable_dict = {
        'filter'    : 0,
    }

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:
def add_user_options(self,dialog):
    option_menu = gtk.OptionMenu()
    self.the_menu = gtk.Menu()

    for item_index in range(10):
        item = _("Item numer %d") % item_index
        menuitem = gtk.MenuItem(item)
        menuitem.show()
        self.the_menu.append(menuitem)

    option_menu.set_menu(self.the_menu)
    option_menu.set_history(self.options_dict['my_first_option'])

    dialog.add_option(_('My first option'),option_menu)

    self.the_string_entry = gtk.Entry()
    if self.options_dict['my_second_option']:
        self.the_string_entry.set_text(self.options_dict['my_second_option'])
    else:
        self.the_string_entry.set_text(_("Empty string"))
    self.the_string_entry.show()
    dialog.add_option(_('My second option'),self.the_string_entry)

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()
  • Finally, the default definitions for the user-adjustable paragraph styles must be defined here, to form a 'default' stylesheet:
def make_default_style(self, default_style):
    f = docgen.FontStyle()
    f.set_size(10)
    f.set_type_face(docgen.FONT_SANS_SERIF)
    p = docgen.ParagraphStyle()
    p.set_font(f)
    p.set_description(_("The style used for the person's name."))
    default_style.add_style("ABC-Name",p)

Использование класса MenuReportOptions

The MenuReportOptions can be used in place of ReportOptions 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 Menu. All these are initialized in the add_menu_options() function (which is a required function when you inherit from MenuReportOptions). For example:

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)

In this example a BooleanListOption object is created that presents the user with a group of check boxes, one is created for each call to add_button(). Finally the object is added to the menu with menu.add_option(). 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 __init__ function. For example, to access the "what_types" that are selected from the menu above you would add the following code:

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()

In the example, the option class is retrieved by the get_option_by_name() function. The string must match the name you passed as the second argument to menu.add_option() when you created the menu. Then a list of the selected item titles is retrieved with get_selected() and stored as a class member for later use.

Реализация

Определение класса параметров отчета

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.

from gramps.gen.plug.report import Report
from gramps.gen.plug.report import ReportOptions
from gramps.gen.lib import Person
from gramps.gen.plug import docgen
from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext


class DbSummaryOptions(ReportOptions):

    def __init__(self, name, database):

        ReportOptions.__init__(self, name, database)

    def make_default_style(self, default_style):

        # Define the title paragraph, named '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 page.'))

        default_style.add_paragraph_style('DBS-Title', para)

        # Define the normal paragraph, named 'DBS-Normal', which uses a
        # 12 point, Serif font.

        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_paragraph_style('DBS-Normal', para)

So create now file report.py and copy there the above code.

Определение класса отчета

The actual implemention of the DbSummary report is rather simple. No additional work needs to be done to initialize the class, so the parent __init__ routine is called.

All the work is done in the write_report() function. This function uses a GrampsCursor to iterate through the map of Person objects and gathers some simple statistics.

The only thing of any complication is the determination of the most common surname. 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, allowing us to find the most common name by looking at the last entry in the list.

class DbSummaryReport(Report):

    def __init__(self, database, options_class, user):

        Report.__init__(self, database, options_class, user)

    def write_report(self):

        males = 0
        females = 0
        total = 0
        surname_map = {}

        for person in self.database.iter_people():
            if person.get_gender() == Person.MALE:
                males += 1
            if person.get_gender() == Person.FEMALE:
                females += 1
            total += 1

            surname = person.get_primary_name().get_surname()

            if surname in surname_map:
                surname_map[surname] += 1
            else:
                surname_map[surname] = 1

        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('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()

        self.doc.start_paragraph('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()

Append the above code to the report.py file

Регистрация отчета

  • Registration is set into a <name>.gpr.py file
  • Registration should define internal name 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...
    • category (text/graphics/code)
    • translated name (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 require_active should be set to True.
  • Finally, both report class and options class should be passed to registration.

Here's the registration statement for our report:

from gramps.gen.plug._pluginreg import register
from gramps.gen.plug._pluginreg import REPORT, STABLE, CATEGORY_TEXT
from gramps.gen.plug._pluginreg import REPORT_MODE_GUI, REPORT_MODE_CLI
from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext

register(REPORT,
         id='RWT_ID',
         name=_("Report-writing tutorial"),
         description=_("Produces a simple database summary"),
         version='1.0',
         gramps_target_version='6.0.6',
         status=STABLE,
         fname='report.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]
         )

There are arguments for the report class and options class. The report_modes 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 report.

The rest of the options should be self-explanatory.

Copy the above code into report.gpr.py file.


Отчет типа Книга ...

To turn a report into one that will work as a book report, you may need to add the following:

from gen.plug.docgen import IndexMark

# Write the title line. 
# Set in INDEX marker so that this section will be identified 
# as a major category if this is included in a Book report.

title = self._('My Report')
mark = IndexMark(title, INDEX_TYPE_TOC, 1)
self.doc.start_paragraph('MYREPORT-section')
self.doc.write_text(title, mark)
...

See an existing book report for more details. DO NOT copy the above code into report.py file!

Ручная установка вашего отчета

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.

Пример вывода отчета

Start Gramps and your tutorial report will be available from the reports menu as

Rwt menu.png

Run the report and this is what the output looks like

Example RWT ID.jpg

Смотрите также