8649: Familygroup report: Add filter option

This commit is contained in:
Paul Franklin 2016-04-11 19:22:37 -07:00
parent 6b86b154fb
commit 7f41373f07
9 changed files with 227 additions and 36 deletions

View File

@ -344,10 +344,12 @@ class DbReadBase(object):
"""
raise NotImplementedError
def get_family_handles(self):
def get_family_handles(self, sort_handles=False):
"""
Return a list of database handles, one handle for each Family in
the database.
If sort_handles is True, the list is sorted by surnames.
"""
raise NotImplementedError

View File

@ -27,7 +27,8 @@ CustomFilters = None
from ..const import CUSTOM_FILTERS
from ._filterlist import FilterList
from ._genericfilter import GenericFilter, GenericFilterFactory, DeferredFilter
from ._genericfilter import (GenericFilter, GenericFilterFactory,
DeferredFilter, DeferredFamilyFilter)
from ._paramfilter import ParamFilter
from ._searchfilter import SearchFilter, ExactSearchFilter

View File

@ -406,3 +406,27 @@ class DeferredFilter(GenericFilter):
if self.name_pair[1]:
return self._(self.name_pair[0]) % self.name_pair[1]
return self._(self.name_pair[0])
class DeferredFamilyFilter(GenericFamilyFilter):
"""
Filter class allowing for deferred translation of the filter name
"""
def __init__(self, filter_name, family_name):
GenericFamilyFilter.__init__(self, None)
self.name_pair = [filter_name, family_name]
def get_name(self, ulocale=glocale):
"""
return the filter name, possibly translated
If ulocale is passed in (a :class:`.GrampsLocale`) then
the translated value will be returned instead.
:param ulocale: allow deferred translation of strings
:type ulocale: a :class:`.GrampsLocale` instance
"""
self._ = ulocale.translation.gettext
if self.name_pair[1]:
return self._(self.name_pair[0]) % self.name_pair[1]
return self._(self.name_pair[0])

View File

@ -5,7 +5,7 @@
# Copyright (C) 2007-2009 Brian G. Matherly
# Copyright (C) 2008 James Friedmann <jfriedmannj@gmail.com>
# Copyright (C) 2010 Jakim Friant
# Copyright (C) 2015 Paul Franklin
# Copyright (C) 2015-2016 Paul Franklin
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -309,3 +309,77 @@ def get_person_filters(person, include_single=True, name_format=None):
the_filters = [all, des, df, ans, com]
the_filters.extend(CustomFilters.get_filters('Person'))
return the_filters
#-------------------------------------------------------------------------
#
# Family Filters
#
#-------------------------------------------------------------------------
def get_family_filters(database, family,
include_single=True, name_format=None):
"""
Return a list of filters that are relevant for the given family
:param database: The database that the family is in.
:type database: DbBase
:param family: the family the filters should apply to.
:type family: :class:`~.family.Family`
:param include_single: include a filter to include the single family
:type include_single: boolean
:param name_format: optional format to control display of person's name
:type name_format: None or int
"""
from ...filters import (GenericFilterFactory, rules, CustomFilters,
DeferredFamilyFilter)
from ...display.name import displayer as name_displayer
if family:
real_format = name_displayer.get_default_format()
if name_format is not None:
name_displayer.set_default_format(name_format)
fhandle = family.get_father_handle()
if fhandle:
father = database.get_person_from_handle(fhandle)
father_name = name_displayer.display(father)
else:
father_name = _("unknown father")
mhandle = family.get_mother_handle()
if mhandle:
mother = database.get_person_from_handle(mhandle)
mother_name = name_displayer.display(mother)
else:
mother_name = _("unknown mother")
gramps_id = family.get_gramps_id()
name = _("%(father_name)s and %(mother_name)s (%(family_id)s)") % {
'father_name': father_name,
'mother_name': mother_name,
'family_id': gramps_id}
name_displayer.set_default_format(real_format)
else:
# Do this in case of command line options query (show=filter)
name = _("FAMILY")
gramps_id = ''
if include_single:
FilterClass = GenericFilterFactory('Family')
filt_id = FilterClass()
filt_id.set_name(name)
filt_id.add_rule(rules.family.HasIdOf([gramps_id]))
all = DeferredFamilyFilter(_T_("Every family"), None)
all.add_rule(rules.family.AllFamilies([]))
# feature request 2356: avoid genitive form
df = DeferredFamilyFilter(_T_("Descendant Families of %s"), name)
df.add_rule(rules.family.IsDescendantOf([gramps_id, 1]))
# feature request 2356: avoid genitive form
ans = DeferredFamilyFilter(_T_("Ancestor Families of %s"), name)
ans.add_rule(rules.family.IsAncestorOf([gramps_id, 1]))
if include_single:
the_filters = [filt_id, all, df, ans]
else:
the_filters = [all, df, ans]
the_filters.extend(CustomFilters.get_filters('Family'))
return the_filters

View File

@ -548,11 +548,12 @@ class FilterProxyDb(ProxyDbBase):
else:
return map(self.get_event_from_handle, self.elist)
def get_family_handles(self):
def get_family_handles(self, sort_handles=False):
"""
Return a list of database handles, one handle for each Family in
the database.
the database. If sort_handles is True, the list is sorted by surnames
"""
# FIXME: flist is not a sorted list of handles
return list(self.flist)
def iter_family_handles(self):

View File

@ -319,18 +319,20 @@ class ProxyDbBase(DbReadBase):
def get_person_handles(self, sort_handles=False):
"""
Return a list of database handles, one handle for each Person in
the database.
the database. If sort_handles is True, the list is sorted by surnames
"""
# FIXME: this is not a sorted list of handles
if self.db.is_open:
return list(self.iter_person_handles())
else:
return []
def get_family_handles(self, sort_handles=True):
def get_family_handles(self, sort_handles=False):
"""
Return a list of database handles, one handle for each Family in
the database.
the database. If sort_handles is True, the list is sorted by surnames
"""
# FIXME: this is not a sorted list of handles
if self.db.is_open:
return list(self.iter_family_handles())
else:

View File

@ -110,6 +110,16 @@ def find_byte_surname(key, data):
return surn.encode('utf-8')
return surn
def find_fullname(key, data):
"""
Creating a fullname from raw data of a person, to use for sort and index
returns a byte string
"""
fullname_data = [(data[3][5][0][0] + ' ' + data[3][4], # surname givenname
data[3][5][0][1], data[3][5][0][2],
data[3][5][0][3], data[3][5][0][4])]
return __index_surname(fullname_data)
def find_surname(key, data):
"""
Creating a surname from raw data of a person, to use for sort and index
@ -254,7 +264,7 @@ class DbBsddbRead(DbReadBase, Callback):
.. method:: get_<object>_handles()
returns a list of handles for the object type, optionally sorted
(for Person, Place, Source and Media objects)
(for Citation, Family, Media, Person, Place, Source, and Tag objects)
.. method:: iter_<object>_handles()
@ -1143,16 +1153,21 @@ class DbBsddbRead(DbReadBase, Callback):
return self.all_handles(self.event_map)
return []
def get_family_handles(self):
def get_family_handles(self, sort_handles=False):
"""
Return a list of database handles, one handle for each Family in
the database.
If sort_handles is True, the list is sorted by surnames.
.. warning:: For speed the keys are directly returned, so on python3
bytestrings are returned!
"""
if self.db_is_open:
return self.all_handles(self.family_map)
handle_list = self.all_handles(self.family_map)
if sort_handles:
handle_list.sort(key=self.__sortbyfamily_key)
return handle_list
return []
def get_repository_handles(self):
@ -1804,6 +1819,24 @@ class DbBsddbRead(DbReadBase, Callback):
return glocale.sort_key(find_surname(handle,
self.person_map.get(handle)))
def __sortbyfamily_key(self, handle):
if isinstance(handle, str):
handle = handle.encode('utf-8')
data = self.family_map.get(handle)
data2 = data[2]
if isinstance(data2, str):
data2 = data2.encode('utf-8')
data3 = data[3]
if isinstance(data3, str):
data3 = data3.encode('utf-8')
if data2: # father handle
return glocale.sort_key(find_fullname(data2,
self.person_map.get(data2)))
elif data3: # mother handle
return glocale.sort_key(find_fullname(data3,
self.person_map.get(data3)))
return ''
def __sortbyplace(self, first, second):
if isinstance(first, str):
first = first.encode('utf-8')

View File

@ -158,7 +158,8 @@ class DictionaryDb(DbGeneric):
## Fixme: implement sort
return [bytes(key, "utf-8") for key in self._person_dict.keys()]
def get_family_handles(self):
def get_family_handles(self, sort_handles=False):
## Fixme: implement sort
return [bytes(key, "utf-8") for key in self._family_dict.keys()]
def get_event_handles(self):

View File

@ -4,8 +4,8 @@
# Copyright (C) 2000-2007 Donald N. Allingham
# Copyright (C) 2007-2008 Brian G. Matherly
# Copyright (C) 2010 Jakim Friant
# Copyright (C) 2013-2016 Paul Franklin
# Copyright (C) 2015 Gerald Kunzmann <gerald@gkunzmann.de>
# Copyright (C) 2013-2016 Paul Franklin
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -39,7 +39,7 @@ from functools import partial
from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.sgettext
from gramps.gen.lib import EventRoleType, EventType, NoteType, Person
from gramps.gen.plug.menu import BooleanOption, FamilyOption
from gramps.gen.plug.menu import BooleanOption, FamilyOption, FilterOption
from gramps.gen.plug.report import Report
from gramps.gen.plug.report import utils as ReportUtils
from gramps.gen.plug.report import MenuReportOptions
@ -70,7 +70,9 @@ class FamilyGroup(Report):
This report needs the following parameters (class variables)
that come in the options class.
family_handle - Handle of the family to write report on.
filter - Filter to be applied to the families of the database.
The option class carries its number, and the function
returning the list of filters.
includeAttrs - Whether to include attributes
name_format - Preferred format to display names
incl_private - Whether to include private data
@ -78,20 +80,14 @@ class FamilyGroup(Report):
years_past_death - Consider as living this many years after death
"""
Report.__init__(self, database, options, user)
self._user = user
menu = options.menu
stdoptions.run_private_data_option(self, menu)
stdoptions.run_living_people_option(self, menu)
self.db = self.database
self.family_handle = None
family_id = menu.get_option_by_name('family_id').get_value()
family = self.db.get_family_from_gramps_id(family_id)
if family:
self.family_handle = family.get_handle()
else:
self.family_handle = None
self.filter = menu.get_option_by_name('filter').get_filter()
get_option_by_name = menu.get_option_by_name
get_value = lambda name:get_option_by_name(name).get_value()
@ -109,8 +105,8 @@ class FamilyGroup(Report):
self.incChiMar = get_value('incChiMar')
self.includeAttrs = get_value('incattrs')
rlocale = self.set_locale(get_value('trans'))
self._ = rlocale.translation.sgettext # needed for English
self._locale = self.set_locale(get_value('trans'))
self._ = self._locale.translation.sgettext # needed for English
stdoptions.run_name_format_option(self, menu)
@ -654,8 +650,22 @@ class FamilyGroup(Report):
self.dump_family(child_family_handle, (generation+1))
def write_report(self):
if self.family_handle:
self.dump_family(self.family_handle, 1)
flist = self.db.get_family_handles(sort_handles=True)
if not self.filter:
fam_list = flist
else:
with self._user.progress(_('Family Group Report'),
_('Applying filter...'),
self.db.get_number_of_families()) as step:
fam_list = self.filter.apply(self.db, flist, step)
if fam_list:
with self._user.progress(_('Family Group Report'),
_('Writing families'),
len(fam_list)) as step:
for family_handle in fam_list:
self.dump_family(family_handle, 1)
self.doc.page_break()
step()
else:
self.doc.start_paragraph('FGR-Title')
self.doc.write_text(self._("Family Group Report"))
@ -663,7 +673,7 @@ class FamilyGroup(Report):
#------------------------------------------------------------------------
#
# MenuReportOptions
# FamilyGroupOptions
#
#------------------------------------------------------------------------
class FamilyGroupOptions(MenuReportOptions):
@ -673,6 +683,10 @@ class FamilyGroupOptions(MenuReportOptions):
"""
def __init__(self, name, dbase):
self.__db = dbase
self.__fid = None
self.__filter = None
self.__recursive = None
MenuReportOptions.__init__(self, name, dbase)
def add_menu_options(self, menu):
@ -682,20 +696,30 @@ class FamilyGroupOptions(MenuReportOptions):
add_option = partial(menu.add_option, category_name)
##########################
family_id = FamilyOption(_("Center Family"))
family_id.set_help(_("The center family for the report"))
add_option("family_id", family_id)
self.__filter = FilterOption(_("Filter"), 0)
self.__filter.set_help(
_("Select the filter to be applied to the report."))
add_option("filter", self.__filter)
self.__filter.connect('value-changed', self.__filter_changed)
stdoptions.add_name_format_option(menu, category_name)
self.__fid = FamilyOption(_("Center Family"))
self.__fid.set_help(_("The center family for the filter"))
add_option("family_id", self.__fid)
self.__fid.connect('value-changed', self.__update_filters)
self._nf = stdoptions.add_name_format_option(menu, category_name)
self._nf.connect('value-changed', self.__update_filters)
self.__update_filters()
stdoptions.add_private_data_option(menu, category_name)
stdoptions.add_living_people_option(menu, category_name)
recursive = BooleanOption(_('Recursive'), False)
recursive.set_help(_("Create reports for all descendants "
self.__recursive = BooleanOption(_('Recursive (down)'), False)
self.__recursive.set_help(_("Create reports for all descendants "
"of this family."))
add_option("recursive", recursive)
add_option("recursive", self.__recursive)
stdoptions.add_localization_option(menu, category_name)
@ -763,6 +787,35 @@ class FamilyGroupOptions(MenuReportOptions):
"information."))
add_option("missinginfo", missinginfo)
def __update_filters(self):
"""
Update the filter list based on the selected family
"""
fid = self.__fid.get_value()
family = self.__db.get_family_from_gramps_id(fid)
nfv = self._nf.get_value()
filter_list = ReportUtils.get_family_filters(self.__db, family,
include_single=True,
name_format=nfv)
self.__filter.set_filters(filter_list)
def __filter_changed(self):
"""
Handle filter change.
If the filter is not family-specific, disable the family option
"""
filter_value = self.__filter.get_value()
if filter_value in [0, 2, 3]: # filters that rely on the center family
self.__fid.set_available(True)
else: # filters that don't
self.__fid.set_available(False)
# only allow recursion if the center family is the only family
if self.__recursive and filter_value == 0:
self.__recursive.set_available(True)
elif self.__recursive:
self.__recursive.set_value(False)
self.__recursive.set_available(False)
def make_default_style(self, default_style):
"""Make default output style for the Family Group Report."""
para = ParagraphStyle()