Учебник по написанию отчетов

From Gramps
(Redirected from Report-writing tutorial/ru)

Этот учебник содержит небольшое упражнение по написанию простейшего отчета на основе инфраструктуры 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 должен наследовать от класса ReportOptions. Но на практике большинство отчетов вместо него наследует от класса MenuReportOptions, который абстрагирует большую часть низкоуровневого кода управления виджетами. В этом учебнике мы будем считать, что класс Options наследует от класса MenuReportOptions

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

Чтобы определить параметры для отчета, необходимо переопределить метод MenuReportOptions.add_menu_options(). После этого класс MenuReportOptions представит пользователю меню для запуска отчета в стандартном интерфейсе. Вы можете генерировать меню используя классы, доступные в пакете gramps.gen.plug.menu, такие как класс BooleanListOption. Так же вы можете использовать стандартные параметры Gramps из модуля gramps.gen.plug.report.stdoptions. Например:

def add_menu_options(self, menu):
    """
    Добавляет параметры к меню запуска этого отчета
    """
    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(_('Unknown gender'), True)
    what_types.add_button(_('Other'), True)
    menu.add_option(category_name, "what_types", what_types)

    stdoptions.add_localization_option(menu, category_name)

В это примере category name испольуется для создания вкладки "Параметры Отчета" в диалоге ввода параметров при запуске отчета.

Создается объект what_types класса BooleanListOption который предоставляет пользователю группу флажков, каждый из которых создан вызовом метода what_types.add_button(). В конце объект добавляется в меню вызовом метода menu.add_option().

Функция stdoptions.add_localization_option() добавляет в то же menu стандартный параметр Gramps для выполнения отчета в другой локализации, чем текущая локализация пользовательского интерфейса.

Теперь, чтобы получить доступ к выбранным значениям после того как пользователь запустил отчет, Вы обращаетесь к объекту меню из конструктора отчета __init__(). Например, чтобы узнать "какие типы" ("what_types") были выбраны в меню, Вы добавляете следующий код:

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

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

        what_types_option = options_class.menu.get_option_by_name('what_types')
        self.what_types = what_types_option.get_selected()

        self.set_locale(
            options_class.menu.get_option_by_name("trans").get_value())

В этом примере объект what_types_option извлекается методом options_class.menu.get_option_by_name(). Строка, передаваемая методу, должна быть такой же как та, которая была передана в качестве второго параметра menu.add_option() когда создавалось меню. После этого метод what_types_option.get_selected() извлекает список названий выбранных пользователем флажков и сохраняет их в переменную объекта self.what_types для последующего использования.

Стандартный параметр Gramps для выбора локализации отчета называется "trans". Его значение передается методу Report.set_locale(), который создает метод self._() для выбранной пользователем локализации. Он затем используется методом write_report() (см ниже).


Определение настраиваемых пользователем стилей

Если отчет использует настраиваемые пользователем стили абзацев, то значения по умолчанию для этих стилей должны быть определены здесь посредством переопределения метода make_default_style(), для формирования таблицы стилей 'по умолчанию':

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)

Реализация

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

В этом учебнике у отчета есть два параметра. Первый, what_types, позволяет пользователю выбрать будет ли отчет содержать количество Мужчин, Женщин, Лиц неизвестного пола и Лиц другого пола. Второй - это стандартный параметр Gramps, который позволяет пользователю задать локализацию отчета, отличную от локализации пользовательского интерфейса. Он добавляется вызовом метода stdoptions.add_localization_option(). Эти два параметра добавляются переопределенным методом add_menu_options().

Чтобы определить два уникальных стиля для отчета, DBS-Title and DBS-Normal, так же переопределяется метод make_default_style(). Оба стиля добавляются вызовом метода default_style.add_paragraph_style().

from gramps.gen.plug.report import Report, MenuReportOptions, stdoptions
from gramps.gen.lib import Person
from gramps.gen.plug import docgen
from gramps.gen.plug.menu import BooleanListOption
from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext


class DbSummaryOptions(MenuReportOptions):

    def add_menu_options(self, menu):

        category_name = _("Report Options")
        what_types = BooleanListOption(_('Include'))
        what_types.add_button(_('Males'), True)
        what_types.add_button(_('Females'), True)
        what_types.add_button(_('Unknown gender'), True)
        what_types.add_button(_('Other'), True)
        menu.add_option(category_name, "what_types", what_types)

        stdoptions.add_localization_option(menu, category_name)

    def make_default_style(self, default_style):

        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)

        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)

Теперь создайте файл report.py и скопируйте в него вышеприведенный код.

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

Фактическая реализация отчета DbSummaryReport довольно проста. Для инициализации класса вызывается родительский метод Report.__init__() а затем из объекта options_class, который имеет класс DbSummaryOptions, извлекаются значения параметров отчета, заданных пользователем. Список имен флажков, выбранных пользователем, присваивается переменной объекта self.what_types. Затем вызывается метод self.set_locale(), которому передается выбранная пользователем локализация, которая должна использоваться для запуска отчета.

Вся работа делается в методе _count(). Он использует итератор self.database.iter_people() для последовательного перебора всех объектов класса Person и сбора некоторой простой статистики. Единственная вещь, которая может рассматриваться как некоторое затруднение, это определение наиболее часто встречающихся фамилий. Для подсчета количества использования каждой фамилии используется словарь Питон. Всякий раз, когда некоторая фамилия встречается, счетчик значений для неё в словаре увеличивается. Результаты затем загружаются в список и сортируются в обратном порядке slist.sort(reverse=True), что позволяет нам найти наиболее часто встречающиеся фамилии просто посмотрев в начало списка.

В заключение метод write_report() выводит собранные данные в соответствии с параметрами, выбранными пользователем отчета. Заметьте, что строки, которые используются в отчете, (такие как "Report Options", "Males" и др.) уже используется где-то в Gramps и были переведены на другие языки. Таким образом, содержание нашего отчета и его параметры оказываются уже переведенными!

class DbSummaryReport(Report):

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

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

        what_types_option = options_class.menu.get_option_by_name('what_types')
        self.what_types = what_types_option.get_selected()

        self.set_locale(
            options_class.menu.get_option_by_name("trans").get_value())

    def _write_normal_paragraph(self, text):
        self.doc.start_paragraph('DBS-Normal')
        self.doc.write_text(text)
        self.doc.end_paragraph()

    def _write_title_paragraph(self, text):
        self.doc.start_paragraph('DBS-Title')
        self.doc.write_text(text)
        self.doc.end_paragraph()

    def _count(self):
        males = 0
        females = 0
        others = 0
        unkn = 0
        total = 0
        surname_map = {}
        for person in self.database.iter_people():

            if person.get_gender() == Person.MALE:
                males += 1
            elif person.get_gender() == Person.FEMALE:
                females += 1
            elif person.get_gender() == Person.OTHER:
                others += 1
            else:
                unkn += 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(reverse=True)
        return (males, females, others, unkn, total, slist)

    def write_report(self):

        males, females, others, unkn, total, slist = self._count()

        self._write_title_paragraph(self._("Database Summary Report"))

        if _('Males') in self.what_types:
            self._write_normal_paragraph(self._('Males: %d') % males)

        if _('Females') in self.what_types:
            self._write_normal_paragraph(self._('Females: %d') % females)

        if _('Unknown gender') in self.what_types:
            self._write_normal_paragraph(
                self._("Individuals with unknown gender: %d") % unkn)

        if _('Other') in self.what_types:
            self._write_normal_paragraph(
                self._("Individuals with other gender: %d") % others)

        self._write_normal_paragraph(
            self._("Number of individuals: %d") % total)

        self._write_normal_paragraph(
            self._('Total unique surnames') + ': %d' % len(slist))

        top_surnames = (', '.join([s[1] for s in slist[:10]]))
        self._write_normal_paragraph(
            self._('Top Surnames') + ' : %s' % top_surnames)

Добавьте код выше в файл report.py.

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

  • Регистрация выполняется файлом <name>.gpr.py
  • Регистрация состоит из единственного вызова функции register()
  • Регистрация должна определить внутренний id (идентификатор) отчёта (предпочтительно это должна быть строка, содержащая не специальные символы ASCII, подходящая для идентификации отчета в командной строке и хранилище параметров, а так же для формирования подходящего имени файла для хранения его собственных стилей).
  • Она так же должна определить для отчета ....
    • категорию category (текстовый/графический/код)
    • переводимое имя name (которое будет показываться в меню)
    • Если отчет требует для запуска наличия активного лица, то параметр require_active должен иметь значение True.
    • Аргумент report_modes должен содержать список из нуля или более следующих режимов:
      • REPORT_MODE_GUI (доступен в Gramps, запущенном в окне, с графическим интерфейсом пользователя)
      • REPORT_MODE_BKI (доступен как отчет раздел Книги)
      • REPORT_MODE_CLI (доступен из интерфейса командной строки)
    • В заключение, имена обеих классов reportclass и optionsclass должны быть переданы для регистрации.

Описание остальных аргументов функции register() находятся здесь. Вот как выглядит вызов функции регистрации нашего отчёта:

from gramps.gen.plug._pluginreg import *
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]
         )

Скопируйте код выше в файл report.gpr.py.


Замечание касательно режима отчета Книга

Для преобразования отчета в пригодный для использования в качестве раздела Книги вам надо добавить REPORT_MODE_BKI в список report_modes выше. Так же вам надо добавить дополнительный код в метод write_report() класса Report, похожий на код приведенный ниже:

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

Посмотрите существующие отчеты, используемые в режиме Книга, на предмет дополнительной информации. НЕ КОПИРУЙТЕ код выше в файл report.py так как мы не собираемся использовать наш отчет в режиме Книга.

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

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.

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

Запустите Gramps и ваш учебный отчет будет включен в меню отчетов как показано ниже

RWT menu ru.png

Запустите отчет и полученный отчет будет похож на показанный ниже:

Example RWT ID ru.jpg

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