Difference between revisions of "Écrire un rapport"

From Gramps
Jump to: navigation, search
m (Définir la classe du rapport)
m (Définir la classe du rapport)
Line 279: Line 279:
 
La mise en place effective du rapport <tt>DbSummary</tt> est assez simple. Pas de travail supplémentaire pour initialiser la classe, de sorte que la routine parent <tt>__init__</tt> est appelée.
 
La mise en place effective du rapport <tt>DbSummary</tt> est assez simple. Pas de travail supplémentaire pour initialiser la classe, de sorte que la routine parent <tt>__init__</tt> est appelée.
  
Tout le travail se fait dans <tt>write_report()</tt>. Cette fonction utilise un <tt>GrampsCursor</tt> pour itérer à travers les <tt>objects</tt>, rassemblant les objets tels de simples statistiques.
+
Tout le travail se fait dans <tt>write_report()</tt>. Cette fonction utilise <tt>GrampsCursor</tt> pour itérer à travers les <tt>objects</tt>, rassemblant les objets tels de simples statistiques.
  
 
La seule chose un peut plus compliqué étant la détermination des patronymes les plus courants. Un dictionnaire Python est utilisé pour stocker le nombre de fois que chaque nom est utilisé. Chaque fois qu'un nom est rencontré, la valeur dans le dictionnaire est incrémentée. Les résultats sont ensuite chargés dans une liste et classés, ce qui nous permet de trouver le nom le plus courant en regardant la dernière inscription dans la liste.
 
La seule chose un peut plus compliqué étant la détermination des patronymes les plus courants. Un dictionnaire Python est utilisé pour stocker le nombre de fois que chaque nom est utilisé. Chaque fois qu'un nom est rencontré, la valeur dans le dictionnaire est incrémentée. Les résultats sont ensuite chargés dans une liste et classés, ce qui nous permet de trouver le nom le plus courant en regardant la dernière inscription dans la liste.

Revision as of 14:39, 6 May 2010

Gramps-notes.png
This page has good information but needs a cleanup. Please make the Gramps Wiki more useful by re‑organizing this page and linking introductory material. Consider also updating the screenshots.

Introduction

Ce tutoriel décrit les bases pour l'écriture d'un simple rapport utilisant l'infrastructure de GRAMPS. Il couvre les processus de gestion d'options, la construction d'un document et de création d'un rapport.

Les objectifs de ce rapport est de créer un rapport de synthèse. Il comportera les informations suivantes :

    * Le nombre de personnes dans la base de données
    * Le nombre de garçons et de filles
    * Le nombre de noms de famille uniques.
    * Le prénom le plus commun.

À partir de la version 3.2, sont également disponibles : un accès simple aux API de base de données, accompagnée de Rapports rapides, Gramplets et Addons.

Aperçu

Avant d'entrer dans les détails, il est utile de noter que le rapport doit comporter trois éléments fondamentaux. Comme expliqué sur la page Addons, les éléments sont dans deux fichiers différents : le fichier code source (càd : report.py) et le fichier d'enregistrement du greffon Gramps (càd : report.gpr.py).

report.py

Rapport de classe

C'est ce code qui contient les données de la base de données GRAMPS et les structure dans le document.

Cette structure peut ensuite être imprimée, visualisée, ou écrite dans une variété de formats.

Cette classe utilise l'interface docgen, fournissant les détails pour le format de sortie.

Options de la classe

C'est le code qui fournit les moyens nécessaires à l'obtention des options pour le rapport, utilisant une variété de mécanismes disponibles.

report.gpr.py

Déclaration d'enregistrement

Ceci initialise le report par un simple appel de register() dans le module.

Il est trivial, mais sans ceci votre rapport ne sera pas disponible dans GRAMPS, même si il est par ailleurs parfaitement écrit.

Un rapport peut être généré potentiellement comme un rapport unique, comme un item de livre GRAMPS, et comme un rapport en ligne de commande. L'enregistrement détermine quels sont les modes activés pour un rapport donné. Le rapport classe ne nécessite aucune connaissance du mode. La classe d'options est là pour fournir des options d'interface pour tous les modes disponibles.

L'interface du document

GRAMPS essaye d'ignorer la sortie du document quel que soit le rapport. En codant la classe docgen, le rapport peut générer le document selon le format désiré par l'utilisateur final. Le document transmis au rapport (self.doc) peut être une page HTML, OpenDocument, PDF ou d'autres formats supportés par l'utilisateur. Le rapport n'a pas à se préoccuper des détails du format de sortie, puisque tous les détails sont pris en charge par l'objet document.

Un document est composé de paragraphes, des tableaux, des graphiques et d'objets. Les tableaux et les graphiques des objets ne seront pas développés dans ce tutoriel.

Le rapport définit une série de paragraphe et styles, ainsi que leur application par défaut. L'utilisateur peut passer outre la définition de chaque modèle, lui permettant de personnaliser le rapport. Chaque style de paragraphe doit être nommé spécifiquement, pour prévenir les collisions lors de l'impression dans un format livre. Il est recommandé de préfixer chaque style de paragraphe avec un code à trois lettres uniques au rapport.

Paragraphe et styles sont définis dans make_default_style() : la fonction de la classe d'options. Les paragraphes sont regroupés dans StyleSheet, qui est défini dans make_default_style(). Pour l'exemple du rapport (DbSummary), les styles de paragraphe sont définies comme suit:

    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)

Définir les classes

La classe du rapport

La classe du rapport doit hériter des contenus du rapport. Le constructeur doit définir deux arguments (hors de la classe elle-même, le plus souvent signalés par un 'self'):

    * La base de données GRAMPS
    * Classe options

Le premier est la base de données de travail. La deuxième est l'instance de la classe d'options définies dans le même rapport, voir la section suivante. Voici l'exemple d'une définition de classe pour un rapport:

  from ReportBase import Report, ReportUtils, ReportOptions

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

La classe du rapport va initialiser plusieurs variables pour l'utilisateur, basées sur les valeurs définies. Il s'agit de :

self.doc 
le document ouvert, prêt pour la sortie. Du type docgen, ceci n'est pas un fichier objet normal.
self.database 
l'objet GrampsDbBase de base de données.
self.options_class 
la classe ReportOptions.

Tout le reste du rapport devant définir les besoins afin de produire le rapport sera obtenu auprès de la classe options_class. Par exemple, vous pouvez avoir besoin d'inclure du code supplémentaires dans la construction de la classe afin d'obtenir les options que vous avez définies pour le rapport.

La classe du rapport doit fournir write_report(). Cette méthode doit copier le contenu du rapport dans le document déjà ouvert.

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

Le reste de la classe est plus l'affaire du développeur. Selon les objectifs et la portée du rapport, il y a une taille du code. Lorsque l'utilisateur génère le rapport dans n'importe quel mode, la construction de la classe va être exécutée, puis la méthode write_report() sera appelée. Donc, si vous avez écrit quelque chose de vraiment important, assurez-vous que ceci est appelée depuis write_report(). Sinon personne ne le verra à part en regardant dans le code.

La classe options

  • La classe Options doit dériver de la classe ReportOptions. En général dans un simple rapport, la classe MenuReportOptions en est dérivée. MenuReportOptions va fournir l'essentiel de la gestion du widget de bas niveau.

Utilisation de ReportOptions

  class OptionsClassName(ReportOptions):
      def __init__(self,name,person_id=None):
          ReportOptions.__init__(self,name,person_id)

Elle doit fixer les nouvelles options spécifiques au rapport, en passant outre set_new_options() qui définit les dictionnaires options_dict et options_help:

      def set_new_options(self):
          # Options specific for this report
          self.options_dict = {
              'my_fist_option'    : 0,
              'my_second_option'  : '',
          }
          self.options_help = {
              'my_fist_option'    : ("=num","Number of something",
                                     [ "First value", "Second value" ],
                                     True),
              'my_second_option'  : ("=str","Some necessary string for the report",
                                     "Whatever String You Wish"),
        }
  • Elle doit également permettre les options "semi-common" utilisées dans le rapport, en passant au-dessus de la méthode enable_options qui définit le dictionnaire enable_dict. Les semi-commons sont les options connues par GRAMPS, mais qui ne sont pas nécessairement présentes dans tous les rapports:
      def enable_options(self):
          # Semi-common options that should be enabled for this report
          self.enable_dict = {
              'filter'    : 0,
          }

Tous les options sont déjà prises en charge par la base de GRAMPS.

  • Pour tout mettre en place de nouvelles options dans la classe d'options, il doit être défini widgets pour fournir les moyens de changer ces options par le biais du dialogue. En outre, il faut définir des méthodes pour extraire les valeurs de ces options dans le widgets et de les mettre ainsi dans la classe variable dictionnaire:
      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()
  • Enfin, les définitions par défaut pour l'utilisateur réglable paragraphe style doit être défini ici, pour former un 'défaut' feuille de 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)

Utilisation de 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 gen.plug.menu. All these are initialzed 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 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.

Implémentation

Définition de la classe Rapport Options

Dans cet exemple, aucune des options spéciales sont requises. Cela rend la classe d'options très simple. Tout ce qui est nécessaire pour définir les styles par défaut.

class DbSummaryOptions(ReportOptions):

    def __init__(self, name, person_id=None):

        ReportOptions.__init__(self, name, person_id)

    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)

Définir la classe du rapport

La mise en place effective du rapport DbSummary est assez simple. Pas de travail supplémentaire pour initialiser la classe, de sorte que la routine parent __init__ est appelée.

Tout le travail se fait dans write_report(). Cette fonction utilise GrampsCursor pour itérer à travers les objects, rassemblant les objets tels de simples statistiques.

La seule chose un peut plus compliqué étant la détermination des patronymes les plus courants. Un dictionnaire Python est utilisé pour stocker le nombre de fois que chaque nom est utilisé. Chaque fois qu'un nom est rencontré, la valeur dans le dictionnaire est incrémentée. Les résultats sont ensuite chargés dans une liste et classés, ce qui nous permet de trouver le nom le plus courant en regardant la dernière inscription dans la liste.

class DbSummaryReport(Report):

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

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

    def write_report(self):

        cursor = self.database.get_person_cursor()

        data = cursor.first()

        males = 0
        females = 0
        total = 0
        surname_map = {}
        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('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()

L'enregistrement du rapport

  • L'enregistrement est défini dans un fichier nom.gpr.py.
  • L'enregistrement devrait définir nom interne du rapport (de préférence, seule chaîne avec des caractères spéciaux, utilisable pour rendre l'identification de la ligne de commande et dans les options de stockage, ainsi que pour former le nom du fichier sain pour stocker ses propres modèles). On doit aussi définir :
    • la catégorie du rapport (texte / graphiques / Code)
    • le nom pour la traduction (celui de l'affichage dans les menus)
    • les modes qui doivent être activés pour le rapport (autonome, livre, ligne de commande).
  • Si le rapport a besoin d'une personne active pour fonctionner, alors require_active doit être défini comme True.
  • Enfin, les deux options de rapport classe et la classe doivent être passées à l'enregistrement.

Voici par exemple la déclaration d'enregistrement: Finalement, les classes options et report doivent passer par l'enregistrement. Voici un exemple d'enregistrement :

register(REPORT,
id    = 'un identifiant unique'
name  = _("Nom du plugin")
description =  _("Produces a ...")
version = '1.0'
gramps_target_version = '3.2'
status = STABLE
fname = 'filename.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]

Deux chaînes définissent le rapport de classe et d'options classe. L'argument report_modes argument est fixé à la somme de bits sage (le OU) de chacun des trois modes possibles: GUI (autonome rapport produit d'GRAMPS cours d'exécution dans une fenêtre), BKI (Livre en question), et CLI (interface de ligne de commande). Cela signifie que le rapport sera disponible dans les trois modes. Le reste devrait être d'explication.

Un exemplaire complet du rapport peut être téléchargé (1.66 KB) pour les essais et l'expérimentation.