gramps/src/plugins/webreport/WebCal.py
Jérôme Rapinat aa30075b18 typo - return translated string
svn: r14302
2010-02-10 16:56:53 +00:00

1753 lines
68 KiB
Python

#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2007 Thom Sturgill
# Copyright (C) 2007-2009 Brian G. Matherly
# Copyright (C) 2008-2009 Rob G. Healey <robhealey1@gmail.com>
# Copyright (C) 2008 Jason Simanek
#
# 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
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# $Id$
"""
Web Calendar generator.
Refactoring. This is an ongoing job until this plugin is in a better shape.
"""
from __future__ import with_statement
#------------------------------------------------------------------------
#
# python modules
#
#------------------------------------------------------------------------
import os, codecs, shutil, re
import datetime, calendar
from gen.ggettext import sgettext as _
from gen.ggettext import ngettext
from itertools import imap
#------------------------------------------------------------------------
#
# Set up logging
#
#------------------------------------------------------------------------
import logging
log = logging.getLogger(".WebPage")
#------------------------------------------------------------------------
#
# GRAMPS module
#
#------------------------------------------------------------------------
from gen.lib import date, Date, Name, Person, NameType, EventType
import const
from ReportBase import Report, ReportUtils, MenuReportOptions, CSS_FILES
from gen.plug.menu import BooleanOption, NumberOption, StringOption, \
EnumeratedListOption, FilterOption, PersonOption, \
DestinationOption, NoteOption
import GrampsLocale
from QuestionDialog import WarningDialog
from Utils import probably_alive, xml_lang, get_researcher
from gui.utils import ProgressMeter
from DateHandler import displayer as _dd
from gen.display.name import displayer as _nd
from DateHandler import displayer as _dd
import libholiday
from libhtml import Html
from libhtmlconst import _CHARACTER_SETS, _CC, _COPY_OPTIONS
# import styled notes from
# src/plugins/lib/libhtmlbackend.py
from libhtmlbackend import HtmlBackend
import Utils
#------------------------------------------------------------------------
#
# constants
#
#------------------------------------------------------------------------
# full clear line for proper styling
fullclear = Html("div", class_ = "fullclear", inline = True)
# Web page filename extensions
_WEB_EXT = ['.html', '.htm', '.shtml', '.php', '.php3', '.cgi']
# Calendar stylesheet names
_CALENDARSCREEN = 'calendar-screen.css'
_CALENDARPRINT = 'calendar-print.css'
#------------------------------------------------------------------------
#
# WebCalReport
#
#------------------------------------------------------------------------
class WebCalReport(Report):
"""
Create WebCalReport object that produces the report.
"""
def __init__(self, database, options):
Report.__init__(self, database, options)
mgobn = lambda name:options.menu.get_option_by_name(name).get_value()
# class to do conversion of styled notes to html markup
self._backend = HtmlBackend()
self.database = database
self.options = options
self.html_dir = mgobn('target')
self.title_text = mgobn('title')
filter_option = options.menu.get_option_by_name('filter')
self.filter = filter_option.get_filter()
self.name_format = mgobn('name_format')
self.ext = mgobn('ext')
self.copy = mgobn('cright')
self.css = mgobn('css')
self.country = mgobn('country')
self.start_dow = mgobn('start_dow')
self.multiyear = mgobn('multiyear')
self.start_year = mgobn('start_year')
self.end_year = mgobn('end_year')
self.maiden_name = mgobn('maiden_name')
self.alive = mgobn('alive')
self.birthday = mgobn('birthdays')
self.anniv = mgobn('anniversaries')
self.home_link = mgobn('home_link')
self.month_notes = [mgobn('note_' + month) \
for month in ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul',
'aug', 'sep', 'oct', 'nov', 'dec']]
self.encoding = mgobn('encoding')
self.fullyear = mgobn('fullyear')
self.makeoneday = mgobn('makeoneday')
# identify researcher name and e-mail address
# as NarrativeWeb already does
researcher = get_researcher()
self.author = researcher.name
if self.author:
self.author = self.author.replace(',,,', '')
self.email = researcher.email
# set to today's date
self.today = date.Today()
self.warn_dir = True # Only give warning once.
self.link_to_narweb = mgobn('link_to_narweb')
self.narweb_prefix = mgobn('prefix')
# self.calendar is a dict; key is the month number
# Each entry in the dict is also a dict; key is the day number.
# The day dict is a list of things to display for that day.
# These things are: birthdays and anniversaries
self.calendar = {}
calendar.setfirstweekday(dow_gramps2iso[self.start_dow])
def get_note_format(self, note):
"""
will get the note from the database, and will return either the
styled text or plain note
"""
# retrieve the body of the note
note_text = note.get()
# styled notes
htmlnotetext = self.styled_note(note.get_styledtext(),
note.get_format())
text = htmlnotetext or Html("p", note_text)
# return text of the note to its callers
return text
#################################################
# Will produce styled notes for WebCal by using:
# src/plugins/lib/libhtmlbackend.py
#################################################
def styled_note(self, styledtext, format):
"""
styledtext : assumed a StyledText object to write
format : = 0 : Flowed, = 1 : Preformatted
style_name : name of the style to use for default presentation
"""
text = str(styledtext)
if not text:
return ''
s_tags = styledtext.get_tags()
#FIXME: following split should be regex to match \n\s*\n instead?
markuptext = self._backend.add_markup_from_styled(text, s_tags,
split='\n\n')
htmllist = Html("div", id="grampsstylednote")
if format == 1:
#preformatted, retain whitespace.
#so use \n\n for paragraph detection
#FIXME: following split should be regex to match \n\s*\n instead?
htmllist += Html('pre', indent=None, inline = True)
for line in markuptext.split('\n\n'):
htmllist += Html("p")
for realline in line.split('\n'):
htmllist += realline
htmllist += Html('br')
elif format == 0:
#flowed
#FIXME: following split should be regex to match \n\s*\n instead?
for line in markuptext.split('\n\n'):
htmllist += Html("p")
htmllist += line
return htmllist
def copy_file(self, from_fname, to_fname, to_dir = ''):
"""
Copy a file from a source to a (report) destination.
If to_dir is not present and if the target is not an archive,
then the destination directory will be created.
Normally 'to_fname' will be just a filename, without directory path.
'to_dir' is the relative path name in the destination root. It will
be prepended before 'to_fname'.
"""
dest = os.path.join(self.html_dir, to_dir, to_fname)
destdir = os.path.dirname(dest)
if not os.path.isdir(destdir):
os.makedirs(destdir)
if from_fname != dest:
shutil.copyfile(from_fname, dest)
elif self.warn_dir:
WarningDialog(
_("Possible destination error") + "\n" +
_("You appear to have set your target directory "
"to a directory used for data storage. This "
"could create problems with file management. "
"It is recommended that you consider using "
"a different directory to store your generated "
"web pages."))
self.warn_dir = False
# ---------------------------------------------------------------------------------------
# Adds Birthdays, Anniversaries, and holidays
# ---------------------------------------------------------------------------------------
def add_day_item(self, text, year, month, day, event):
"""
adds birthdays, anniversaries, and holidays to their perspective lists
text -- line to be added
year, month, day -- date to add the text to
event -- one of 'BirthDay', 'Anniversary', or 'Holiday'
"""
# This may happen for certain "about" dates.
# Use first day of the month
if day == 0:
day = 1
# determine which dictionary to use???
if event in ['Birthday', 'Anniversary']:
month_dict = self.calendar.get(month, {})
else:
month_dict = self.holidays.get(month, {})
day_list = month_dict.get(day, [])
if month > 0:
try:
event_date = Date(year, month, day)
except ValueError:
event_date = Date.EMPTY
else:
event_date = Date.EMPTY # Incomplete date....
day_list.append((text, event, event_date))
month_dict[day] = day_list
# determine which dictionary to add it to???
if event in ['Birthday', 'Anniversary']:
self.calendar[month] = month_dict
else:
self.holidays[month] = month_dict
# ---------------------------------------------------------------------------------------
# Retrieves Holidays from the Holiday file,
# src/plugins/lib/holidays.xml
# ---------------------------------------------------------------------------------------
def __get_holidays(self, year):
# _('translation')
self.progress.set_pass((_('Calculating Holidays for year %04d') % year), 365)
""" Get the holidays for the specified country and year """
holiday_table = libholiday.HolidayTable()
country = holiday_table.get_countries()[self.country]
holiday_table.load_holidays(year, country)
for month in range(1, 13):
for day in range(1, 32):
holiday_names = holiday_table.get_holidays(month, day)
for holiday_name in holiday_names:
self.add_day_item(holiday_name, year, month, day, 'Holiday')
# increment progress bar
self.progress.step()
# ---------------------------------------------------------------------------------------
# Copies all of the calendar files for all calendars
# ---------------------------------------------------------------------------------------
def copy_calendar_files(self):
"""
Copies all the necessary stylesheets and images for these calendars
"""
# Copy the screen stylesheet
if self.css:
fname = os.path.join(const.DATA_DIR, self.css)
self.copy_file(fname, _CALENDARSCREEN, "styles")
# copy print stylesheet
fname = os.path.join(const.DATA_DIR, "Web_Print-Default.css")
self.copy_file(fname, _CALENDARPRINT, "styles")
# set imgs to empty
imgs = []
if self.css == "Web_Mainz.css":
# Mainz stylesheet graphics
# will only be used if Mainz is slected as the stylesheet
imgs += ["Web_Mainz_Bkgd.png", "Web_Mainz_Header.png",
"Web_Mainz_Mid.png", "Web_Mainz_MidLight.png"]
# Copy GRAMPS favicon
imgs += ['favicon.ico']
# copy copyright image
if 0 < self.copy <= len(_CC):
imgs += ['somerights20.gif']
for fname in imgs:
from_path = os.path.join(const.IMAGE_DIR, fname)
self.copy_file(from_path, fname, "images")
# ---------------------------------------------------------------------------------------
# Creates file name passed to it
# ---------------------------------------------------------------------------------------
def create_file(self, fname, subdir):
"""
Create a file in the html_dir tree.
If the directory does not exist, create it.
fname -- filename to be created
subdir -- any subdirs to be added
"""
fname = os.path.join(self.html_dir, subdir, fname)
if not _has_webpage_extension(fname):
fname += self.ext
destdir = os.path.dirname(fname)
if not os.path.isdir(destdir):
os.makedirs(destdir)
of = codecs.EncodedFile(open(fname, "w"), 'utf-8', self.encoding, 'xmlcharrefreplace')
return of
# ---------------------------------------------------------------------------------------
# Closes all file name passed to it
# ---------------------------------------------------------------------------------------
def close_file(self, of):
""" will close whatever filename is passed to it """
of.close()
# ---------------------------------------------------------------------------------------
# Beginning of Calendar Creation
# ---------------------------------------------------------------------------------------
def write_header(self, nr_up, body_id, title, add_print=True):
"""
This creates the header for the Calendars
'nr_up' - number of directory levels up, started from current page, to the
root of the directory tree (i.e. to self.html_dir).
title -- to be inserted into page header section
add_print -- whether to add printer stylesheet or not
* only webcalendar() and one_day() only!
"""
# number of subdirectories up to reach root
subdirs = '../'*nr_up
# Header contants
xmllang = xml_lang()
_META1 = 'name="generator" content="%s %s %s"' % \
(const.PROGRAM_NAME, const.VERSION, const.URL_HOMEPAGE)
_META2 = 'name="author" content="%s"' % self.author
page, head, body = Html.page(title, self.encoding, xmllang)
# GRAMPS favicon
fname1 = os.path.join(subdirs, "images", "favicon.ico")
# _CALENDARSCREEN stylesheet
fname2 = os.path.join(subdirs, "styles", _CALENDARSCREEN)
# create additional meta tags
meta = Html("meta", attr = _META1) + (
Html("meta", attr = _META2, indent=False)
)
# links for GRAMPS favicon and stylesheets
links = Html("link", rel='shortcut icon', href=fname1,
type = "image/x-icon") + (
Html("link",rel="stylesheet", href=fname2, type="text/css", media= "screen",
indent = False)
)
# add printer stylesheet to webcalendar() and one_day() only
if add_print:
fname = os.path.join(subdirs, "styles", _CALENDARPRINT)
links += Html("link",rel="stylesheet", href=fname,type="text/css", media="print",
indent = False)
# add meta tags and links to head section
head += (meta, links)
# replace standard body element with custom one
body.attr = 'id="%s"' % body_id
# start header division section
header = Html("div", id="header") + (
# page title
Html("h1", title, id = "SiteTitle", inline = True)
)
body += header
# Created for ?
msg = None
if self.author and self.email:
msg = _('Created for <a href="mailto:%(email)s?'
'subject=WebCal">%(author)s</a>') % {'email' : self.email,
'author' : self.author}
elif self.author:
msg = _('Created for %(author)s') % {'author' : self.author}
if msg is not None:
header += Html("p", msg, id="CreatorInfo")
# return to its callers; either webcalendar(), year_glance(), or one_day()
return page, body
# ---------------------------------------------------------------------------------------
# Creates year navigation, if multiyear
# ---------------------------------------------------------------------------------------
def year_navigation(self, nr_up, currentsection):
"""
This will create the year navigation menu bar for a total of seventeen (17) years
nr_up = number of directories up to reach root directory
currentsection = proper styling of this navigation bar
"""
# limit number of years to eighteen (18) years and only one row of years
nyears = ((self.end_year - self.start_year) + 1)
num_years = nyears if 0 < nyears < 19 else 18
# begin year division and begin unordered list
with Html("div", id = "subnavigation") as section:
unordered = Html("ul")
section += unordered
for cal_year in xrange(self.start_year, (self.start_year + num_years)):
url = ''
# begin subdir level
subdirs = ['..'] * nr_up
subdirs.append(str(cal_year))
# each year will link to current month.
# this will always need an extension added
full_month_name = get_full_month_name(self.today.get_month())
# Note. We use '/' here because it is a URL, not a OS dependent
# pathname.
url = '/'.join(subdirs + [full_month_name]) + self.ext
# Figure out if we need <li class="CurrentSection"> or just plain <li>
cs = str(cal_year) == currentsection and 'class="CurrentSection"' or ''
unordered += Html("li", attr=cs, inline = True) + (
# create hyperlink
Html("a", str(cal_year), href = url, title = "Year %04d" % cal_year, inline = True)
)
# return yearnav to its caller
return section
# ---------------------------------------------------------------------------------------
# Creates month navigation for all Calendars
# ---------------------------------------------------------------------------------------
def month_navigation(self, nr_up, year, currentsection, add_home):
"""
Will create and display the navigation menu bar
of = calendar filename being created
nr_up = number of directories up to reach root directory
year = year being created
currentsection = month name being created for proper CSS styling
use_home = if creating a link to home
-- a link to root directory of website
"""
navs = []
# An optional link to a home page
navs.append((self.home_link, _('html|Home'), add_home))
navs.extend((month, month, True) for month in range(1,13))
# Add a link for year_glance() if requested
navs.append(('fullyearlinked', _('Year Glance'), self.fullyear))
# begin month subnavigation
with Html("div", id = "navigation") as section:
unordered = Html("ul")
section += unordered
navs = [(u, n) for u, n, c in navs if c]
for url_fname, nav_text in navs:
if type(url_fname) == int:
url_fname = get_full_month_name(url_fname)
if type(nav_text) == int:
nav_text = get_short_month_name(nav_text)
# Note. We use '/' here because it is a URL, not a OS dependent pathname
# need to leave home link alone, so look for it ...
url = url_fname
add_subdirs = True
if not (url.startswith('http:') or url.startswith('/')):
add_subdirs = not any(url.endswith(ext)
for ext in _WEB_EXT)
# whether to add subdirs or not???
if add_subdirs:
subdirs = ['..'] * nr_up
subdirs.append(str(year))
url = '/'.join(subdirs + [url_fname])
if not _has_webpage_extension(url):
url += self.ext
# Figure out if we need <li class="CurrentSection"> or just plain <li>
cs = url_fname == currentsection and 'class="CurrentSection"' or ''
unordered += Html("li", attr = cs, inline = True) + (
# create hyperlink
Html("a", nav_text, href = url, title=url_fname, inline = True)
)
# return monthnav to its caller
return section
# ---------------------------------------------------------------------------------------
# Creates the Calendar Table
# ---------------------------------------------------------------------------------------
def calendar_build(self, cal, year, month):
"""
This does the work of building the calendar
@param: cal - either "yg" year_glance(), or "wc" webcalendar()
@param: year -- year being created
@param: month - month number 1, 2, .., 12
"""
# define names for long and short month names
full_month_name = get_full_month_name(month)
abbr_month_name = get_short_month_name(month)
# dow (day-of-week) uses Gramps numbering, sunday => 1, etc
start_dow = self.start_dow
col2day = [(x-1)%7+1 for x in range(start_dow, start_dow + 7)]
def get_class_for_daycol(col):
""" Translate a Gramps day number into a HTMLclass """
day = col2day[col]
if day == 1:
return "weekend sunday"
elif day == 7:
return "weekend saturday"
return "weekday"
def get_name_for_daycol(col):
""" Translate a Gramps day number into a HTMLclass """
day = col2day[col]
return day_names[day]
# Note. GrampsLocale has sunday => 1, monday => 2, etc
# We slice out the first empty element.
day_names = GrampsLocale.long_days
def __get_previous_month_day(year, month, day_col):
if month == 1:
prevmonth = calendar.monthcalendar(year - 1, 12)
else:
prevmonth = calendar.monthcalendar(year, month-1)
num_weeks = len(prevmonth)
lastweek_prevmonth = prevmonth[num_weeks - 1]
previous_month_day = lastweek_prevmonth[day_col]
# return previous month day number based on day_col
# day_col is based on range(0 - 6)
return previous_month_day
def __get_next_month_day(year, month, day_col):
if month == 12:
nextmonth = calendar.monthcalendar(year + 1, 1)
else:
nextmonth = calendar.monthcalendar(year, month + 1)
firstweek_nextmonth = nextmonth[0]
next_month_day = firstweek_nextmonth[day_col]
# return next month day number based on day_col
# day_col is based on range(0 - 6)
return next_month_day
# Begin calendar head. We'll use the capitalized name, because here it
# seems appropriate for most countries.
month_name = full_month_name.capitalize()
th_txt = month_name
if cal == 'wc': # webcalendar()
if not self.multiyear:
th_txt = '%s %d' % (month_name, year)
# begin calendar table and table head
with Html("table", class_ = "calendar", id = month_name) as table:
thead = Html("thead")
table += thead
trow = Html("tr") + (
Html("th", th_txt, class_ ='monthName', colspan=7, inline = True)
)
thead += trow
# Calendar weekday names header
trow = Html("tr")
thead += trow
for day_col in range(7):
dayclass = get_class_for_daycol(day_col)
dayname = get_name_for_daycol(day_col)
trow += Html("th", class_ =dayclass, inline = True) + (
Html('abbr', dayname[0], title = dayname)
)
# begin table body
tbody = Html("tbody")
table += tbody
# get first of the month and month information
current_date, current_ord, monthinfo = get_first_day_of_month(year, month)
# begin calendar table rows, starting week0
nweeks = len(monthinfo)
for week_row in range(0, nweeks):
week = monthinfo[week_row]
# if you look this up in wikipedia, the first week is called week0
trow = Html("tr", class_ = "week%02d" % week_row)
tbody += trow
# begin calendar day column
for day_col in range(0, 7):
dayclass = get_class_for_daycol(day_col)
# day number, can also be a zero -- a day before or after month
day = week[day_col]
# start the beginning variable for <td>, table cell
tcell_id = "%s%02d" % (abbr_month_name, day)
# add calendar date division
datediv = Html("div", day, class_ = "date", inline = True)
# a day in the previous or next month
if day == 0:
# day in previous month
specday = __get_previous_month_day(year, month, day_col) if week_row == 0 \
else __get_next_month_day(year, month, day_col)
specclass = "previous " if week_row == 0 else "Next "
specclass += dayclass
# continue table cell, <td>, without id tag
tcell = Html("td", class_ = specclass, inline = True) + (
# adds date for previous and next month days
Html("div", specday, class_ = "date", inline = True)
)
# normal day number in current month
else:
thisday = datetime.date.fromordinal(current_ord)
# Something this month
if thisday.month == month:
holiday_list = self.holidays.get(month, {}).get(thisday.day, [])
bday_anniv_list = self.calendar.get(month, {}).get(thisday.day, [])
# date is an instance because of subtracting abilities in date.py
event_date = Date(thisday.year, thisday.month, thisday.day)
# get events for this day
day_list = get_day_list(event_date, holiday_list, bday_anniv_list)
# is there something this day?
if day_list:
hilightday = 'highlight ' + dayclass
tcell = Html("td", id = tcell_id, class_ = hilightday)
# Year at a Glance
if cal == "yg":
# make one day pages and hyperlink
if self.makeoneday:
# create yyyymmdd date string for
# "One Day" calendar page filename
fname_date = '%04d%02d%02d' % (year,month,day) + self.ext
# create hyperlink to one_day()
fname_date = full_month_name + '/' + fname_date
tcell += Html("a", datediv, href = fname_date, inline = True)
# only year_glance() needs this to create the one_day() pages
self.one_day(event_date, fname_date, day_list)
# just year_glance(), but no one_day() pages
else:
# continue table cell, <td>, without id tag
tcell = Html("td", class_ = hilightday, inline = True) + (
# adds date division
Html("div", day, class_ = "date", inline = True)
)
# WebCal
else:
# add date to table cell
tcell += datediv
# list the events
unordered = Html("ul")
tcell += unordered
for nyears, date, text, event in day_list:
unordered += Html("li", text, inline = False if event == 'Anniversary'
else True)
# no events for this day
else:
# create empty day with date
tcell = Html("td", class_ = dayclass, inline = True) + (
# adds date division
Html("div", day, class_ = "date", inline = True)
)
# nothing for this month
else:
tcell = Html("td", class_ = dayclass) + (
# adds date division
Html("div", day, class_ = "date", inline = True)
)
# attach table cell to table row
# close the day column
trow += tcell
# change day number
current_ord += 1
if cal == "yg":
for weeks in xrange(nweeks, 6):
# each calendar must have six weeks for proper styling and alignment
with Html("tr", class_ = "week%02d" % (weeks + 1)) as six_weeks:
tbody += six_weeks
for emptydays in range(7):
six_weeks += Html("td", class_ = "emptyDays", inline = True)
# return calendar table to its callers
return table
# ---------------------------------------------------------------------------------------
# Creates the Web Calendars; the primary part of this plugin
# ---------------------------------------------------------------------------------------
def webcalendar(self, year):
"""
This method provides information and header/ footer to the calendar month
year -- year being created
"""
# do some error correcting if needed
if self.multiyear:
if self.end_year < self.start_year:
self.end_year = self.start_year
nr_up = 1 # Number of directory levels up to get to self.html_dir / root
# generate progress pass for "WebCal"
self.progress.set_pass(_('Formatting months ...'), 12)
for month in range(1, 13):
# Name the file, and create it
cal_fname = get_full_month_name(month)
of = self.create_file(cal_fname, str(year))
# Add xml, doctype, meta and stylesheets
# body has already been added to webcal already once
webcal, body = self.write_header(nr_up, 'WebCal', self.title_text)
# create Year Navigation menu
if (self.multiyear and ((self.end_year - self.start_year) > 0)):
body += self.year_navigation(nr_up, str(year))
# Create Month Navigation Menu
# identify currentsection for proper highlighting
currentsection = get_full_month_name(month)
body += self.month_navigation(nr_up, year, currentsection, True)
# build the calendar
monthly_calendar = self.calendar_build("wc", year, month)
body += monthly_calendar
# create note section for webcalendar()
note = self.month_notes[month-1].strip()
if note:
note = self.database.get_note_from_gramps_id(note)
note = self.get_note_format(note)
# table foot section
cal_foot = Html("tfoot")
monthly_calendar += cal_foot
trow = Html("tr") + (
Html("td", note, colspan=7, inline = True)
)
cal_foot += trow
# create blank line for stylesheets
# create footer division section
footer = self.write_footer(nr_up)
body += (fullclear, footer)
# send calendar page to web output
# and close the file
self.XHTMLWriter(webcal, of)
# increase progress bar
self.progress.step()
# ---------------------------------------------------------------------------------------
# Creates Year At A Glance Calendar
# ---------------------------------------------------------------------------------------
def year_glance(self, year):
"""
This method will create the Full Year At A Glance Page...
year -- year being created
"""
nr_up = 1 # Number of directory levels up to get to root
# generate progress pass for "Year At A Glance"
self.progress.set_pass(_('Creating Year At A Glance calendar'), 12)
# Name the file, and create it
of = self.create_file('fullyearlinked', str(year))
# page title
title = _("%(year)d, At A Glance") % {'year' : year}
# Create page header
# body has already been added to yearglance already once
yearglance, body = self.write_header(nr_up, 'fullyearlinked', title, False)
# create Year Navigation menu
if (self.multiyear and ((self.end_year - self.start_year) > 0)):
body += self.year_navigation(nr_up, str(year))
# Create Month Navigation Menu
# identify currentsection for proper highlighting
body += self.month_navigation(nr_up, year, "fullyearlinked", True)
msg = (_('This calendar is meant to give you access '
'to all your data at a glance compressed into one page. Clicking '
'on a date will take you to a page that shows all the events for '
'that date, if there are any!\n'))
# page description
body += Html("div", class_ = "content") + (
# message line
Html("p", msg, id='description')
)
for month in range(1, 13):
# build the calendar
monthly_calendar = self.calendar_build("yg", year, month)
body += monthly_calendar
# increase progress bar
self.progress.step()
# create blank line for stylesheets
# write footer section
footer = self.write_footer(nr_up)
body += (fullclear, footer)
# send calendar page to web output
# and close the file
self.XHTMLWriter(yearglance, of)
# ---------------------------------------------------------------------------------------
# Creates the individual days for year_glance()
# ---------------------------------------------------------------------------------------
def one_day(self, event_date, fname_date, day_list):
"""
This method creates the One Day page for "Year At A Glance"
event_date -- date for the listed events
fname_date -- filename date from calendar_build()
day_list - a combination of both dictionaries to be able to create one day
nyears, date, text, event --- are necessary for figuring the age or years married
for each year being created...
"""
nr_up = 2 # number of directory levels up to get to root
# get year and month from event_date for use in this section
year = event_date.get_year()
month = event_date.get_month()
# Name the file, and crate it (see code in calendar_build)
od = self.create_file(fname_date, str(year))
# page title
title = _('One Day Within A Year')
# create page header
oneday, body = self.write_header(nr_up, 'OneDay', title)
# create Year Navigation menu
if (self.multiyear and ((self.end_year - self.start_year) > 0)):
body += self.year_navigation(nr_up, str(year))
# Create Month Navigation Menu
# identify currentsection for proper highlighting
currentsection = get_full_month_name(month)
body += self.month_navigation(nr_up, year, currentsection, True)
# set date display as in user prevferences
body += Html("h3", _dd.display(event_date), inline = True)
# list the events
ordered = Html("ol")
body += ordered
for nyears, date, text, event in day_list:
ordered += Html("li", text, inline = False if event == 'Anniversary' else True)
# create blank line for stylesheets
# write footer section
footer = self.write_footer(nr_up)
body += (fullclear, footer)
# send calendar page to web output
# and close the file
self.XHTMLWriter(oneday, od)
######################################################################################
# Support Functions for linking to Narratted Web Site
######################################################################################
def build_url_fname_html(self, fname, subdir=None, prefix=None):
return self.build_url_fname(fname, subdir, prefix) + self.ext
def build_url_fname(self, fname, subdir, prefix = None):
"""
Create part of the URL given the filename and optionally the subdirectory.
If the subdirectory is given, then two extra levels of subdirectory are inserted
between 'subdir' and the filename. The reason is to prevent directories with
too many entries.
If 'prefix' is set, then is inserted in front of the result.
The extension is added to the filename as well.
Notice that we do NOT use os.path.join() because we're creating a URL.
Imagine we run gramps on Windows (heaven forbits), we don't want to
see backslashes in the URL.
"""
if (Utils.win):
fname = fname.replace('\\',"/")
subdirs = self.build_subdirs(subdir, fname)
return (prefix or '') + "/".join(subdirs + [fname])
def build_subdirs(self, subdir, fname):
"""
If subdir is given, then two extra levels of subdirectory are inserted
between 'subdir' and the filename. The reason is to prevent directories with
too many entries.
For example, this may return ['ppl', '8', '1'] given 'ppl', "aec934857df74d36618"
"""
subdirs = []
if subdir:
subdirs.append(subdir)
subdirs.append(fname[-1].lower())
subdirs.append(fname[-2].lower())
return subdirs
# ---------------------------------------------------------------------------------------
# Get person's short name
# ---------------------------------------------------------------------------------------
def get_name(self, person, maiden_name = None):
"""
Return person's name, unless maiden_name given, unless married_name
listed.
person -- person to get short name from
maiden_name -- either a woman's maiden name or man's surname
"""
# Get all of a person's names:
primary_name = person.primary_name
married_name = None
names = [primary_name] + person.get_alternate_names()
for name in names:
if int(name.get_type()) == NameType.MARRIED:
married_name = name
break
# Now, decide which to use:
if maiden_name is not None:
if married_name is not None:
name = Name(married_name)
else:
name = Name(primary_name)
name.set_surname(maiden_name)
else:
name = Name(primary_name)
name.set_display_as(self.name_format)
return _nd.display_name(name)
# ---------------------------------------------------------------------------------------
# The database slave; Gathers information for calendars
# ---------------------------------------------------------------------------------------
def collect_data(self, this_year):
"""
This method runs through the data, and collects the relevant dates
and text.
"""
db = self.database
people = db.iter_person_handles()
self.progress.set_pass(_('Applying Filter...'), db.get_number_of_people())
people = self.filter.apply(db, people, self.progress)
self.progress.set_pass(_("Reading database..."), len(people))
for person in imap(db.get_person_from_handle, people):
self.progress.step()
family_list = person.get_family_handle_list()
birth_ref = person.get_birth_ref()
birth_date = Date.EMPTY
if birth_ref:
birth_event = db.get_event_from_handle(birth_ref.ref)
birth_date = birth_event.get_date_object()
# determine birthday information???
if (self.birthday and birth_date is not Date.EMPTY):
year = birth_date.get_year() or this_year
month = birth_date.get_month() or 1
day = birth_date.get_day() or 1
# date to figure if someone is still alive
# current year of calendar, month nd day is their birth month and birth day
prob_alive_date = Date(this_year, month, day)
# add some things to handle maiden name:
father_surname = None # husband, actually
if person.gender == Person.FEMALE:
# get husband's last name:
if self.maiden_name in ['spouse_first', 'spouse_last']:
if family_list:
if self.maiden_name == 'spouse_first':
fhandle = family_list[0]
else:
fhandle = family_list[-1]
fam = db.get_family_from_handle(fhandle)
father_handle = fam.get_father_handle()
mother_handle = fam.get_mother_handle()
if mother_handle == person.handle:
if father_handle:
father = db.get_person_from_handle(father_handle)
if father is not None:
father_surname = _get_regular_surname(person.gender, father.primary_name)
short_name = self.get_name(person, father_surname)
alive = probably_alive(person, db, prob_alive_date)
if (self.alive and alive) or not self.alive:
# add link to NarrativeWeb
if self.link_to_narweb:
text = Html("a", short_name,
href = self.build_url_fname_html(person.handle, "ppl",
prefix = self.narweb_prefix))
else:
text = short_name
self.add_day_item(text, year, month, day, 'Birthday')
# add anniversary if requested
if self.anniv:
for fhandle in family_list:
fam = db.get_family_from_handle(fhandle)
father_handle = fam.get_father_handle()
mother_handle = fam.get_mother_handle()
if father_handle == person.handle:
spouse_handle = mother_handle
else:
continue # with next person if this was the marriage event
if spouse_handle:
spouse = db.get_person_from_handle(spouse_handle)
if spouse:
spouse_name = self.get_name(spouse)
short_name = self.get_name(person)
# will return a marriage event or False if not married any longer
marriage_event = get_marriage_event(db, fam)
if marriage_event:
event_date = marriage_event.get_date_object()
if event_date is not Date.EMPTY:
year = event_date.get_year()
month = event_date.get_month()
day = event_date.get_day()
# date to figure if someone is still alive
prob_alive_date = Date(this_year, month, day)
if self.link_to_narweb:
spouse_name = Html("a", spouse_name,
href = self.build_url_fname_html(spouse_handle, 'ppl',
prefix = self.narweb_prefix))
short_name = Html("a", short_name,
href = self.build_url_fname_html(person.handle, 'ppl',
prefix = self.narweb_prefix))
alive1 = probably_alive(person, db, prob_alive_date)
alive2 = probably_alive(spouse, db, prob_alive_date)
if ((self.alive and alive1 and alive2) or not self.alive):
text = _('%(spouse)s and %(person)s') % {
'spouse' : spouse_name,
'person' : short_name}
self.add_day_item(text, year, month, day, 'Anniversary')
# ---------------------------------------------------------------------------------------
# Closes the Calendars; the end
# ---------------------------------------------------------------------------------------
def write_footer(self, nr_up):
"""
Writes the footer section of the pages
'nr_up' - number of directory levels up, started from current page, to the
root of the directory tree (i.e. to self.html_dir).
"""
# begin calendar footer
with Html("div", id = "footer") as footer:
# Display date as user set in preferences
msg = _('Generated by <a href="http://gramps-project.org">'
'Gramps</a> on %(date)s') % {'date' : _dd.display(date.Today())}
footer += Html("p", msg, id = 'createdate')
copy_nr = self.copy
text = ''
if copy_nr == 0:
if self.author:
text = "&copy; %s %s" % (self.today.get_year(), self.author)
elif 0 < copy_nr < len(_CC):
subdirs = ['..'] * nr_up
# Note. We use '/' here because it is a URL, not a OS dependent pathname
fname = '/'.join(subdirs + ['images'] + ['somerights20.gif'])
text = _CC[copy_nr] % {'gif_fname' : fname}
else:
text = "&copy; %s %s" % (self.today.get_year(), self.author)
footer += Html("p", text, id = 'copyright')
# return footer to its callers
return footer
# ---------------------------------------------------------------------------------------
# # Web Page Fortmatter and writer
# ---------------------------------------------------------------------------------------
def XHTMLWriter(self, page, of):
"""
This function is simply to make the web page look pretty and readable
It is not for the browser, but for us, humans
"""
# writes the file out from the page variable; Html instance
page.write(lambda line: of.write(line + '\n'))
# close the file now...
self.close_file(of)
# ---------------------------------------------------------------------------------------
# The work horse of this plugin; stages everything
# ---------------------------------------------------------------------------------------
def write_report(self):
"""
The short method that runs through each month and creates a page.
"""
# Create progress meter bar
self.progress = ProgressMeter(_("Web Calendar Report"), '')
# get data from database for birthdays/ anniversaries
self.collect_data(self.start_year)
# Copy all files for the calendars being created
self.copy_calendar_files()
if self.multiyear:
# limit number of years to eighteen (18) years and only one row of years
nyears = ((self.end_year - self.start_year) + 1)
num_years = nyears if 0 < nyears < 19 else 18
for cal_year in xrange(self.start_year, (self.start_year + num_years)):
# initialize the holidays dict to fill:
self.holidays = {}
# get the information, zero is equal to None
if self.country != 0:
self.__get_holidays(cal_year)
# create webcalendar() calendar pages
self.webcalendar(cal_year)
# create "Year At A Glance" and
# "One Day" calendar pages
if self.fullyear:
self.year_glance(cal_year)
# a single year
else:
cal_year = self.start_year
self.holidays = {}
# get the information, first from holidays:
if self.country != 0:
self.__get_holidays(cal_year)
# create webcalendar() calendar pages
self.webcalendar(cal_year)
# create "Year At A Glance" and
# "One Day" calendar pages
if self.fullyear:
self.year_glance(cal_year)
# Close the progress meter
self.progress.close()
# ---------------------------------------------------------------------------------------
# WebCalOptions; Creates the Menu
#----------------------------------------------------------------------------------------
class WebCalOptions(MenuReportOptions):
"""
Defines options and provides handling interface.
"""
def __init__(self, name, dbase):
self.__db = dbase
self.__pid = None
self.__filter = None
self.__links = None
self.__prefix = None
MenuReportOptions.__init__(self, name, dbase)
def add_menu_options(self, menu):
"""
Add options to the menu for the web calendar.
"""
self.__add_report_options(menu)
self.__add_content_options(menu)
self.__add_notes_options(menu)
self.__add_advanced_options(menu)
def __add_report_options(self, menu):
"""
Options on the "Report Options" tab.
"""
category_name = _("Report Options")
target = DestinationOption( _("Destination"),
os.path.join(const.USER_HOME, "WEBCAL"))
target.set_help( _("The destination directory for the web files"))
target.set_directory_entry(True)
menu.add_option(category_name, "target", target)
title = StringOption(_('Calendar Title'), _('My Family Calendar'))
title.set_help(_("The title of the calendar"))
menu.add_option(category_name, "title", title)
self.__filter = FilterOption(_("Filter"), 0)
self.__filter.set_help(
_("Select filter to restrict people that appear on calendar"))
menu.add_option(category_name, "filter", self.__filter)
self.__filter.connect('value-changed', self.__filter_changed)
self.__pid = PersonOption(_("Filter Person"))
self.__pid.set_help(_("The center person for the filter"))
menu.add_option(category_name, "pid", self.__pid)
self.__pid.connect('value-changed', self.__update_filters)
self.__update_filters()
# We must figure out the value of the first option before we can
# create the EnumeratedListOption
fmt_list = _nd.get_name_format()
name_format = EnumeratedListOption(_("Name format"), fmt_list[0][0])
for num, name, fmt_str, act in fmt_list:
name_format.add_item(num, name)
name_format.set_help(_("Select the format to display names"))
menu.add_option(category_name, "name_format", name_format)
ext = EnumeratedListOption(_("File extension"), ".html" )
for etype in _WEB_EXT:
ext.add_item(etype, etype)
ext.set_help( _("The extension to be used for the web files"))
menu.add_option(category_name, "ext", ext)
cright = EnumeratedListOption(_('Copyright'), 0 )
for index, copt in enumerate(_COPY_OPTIONS):
cright.add_item(index, copt)
cright.set_help( _("The copyright to be used for the web files"))
menu.add_option(category_name, "cright", cright)
css = EnumeratedListOption(_('StyleSheet'), CSS_FILES[0][1])
for style in CSS_FILES:
css.add_item(style[1], style[0])
css.set_help( _('The Style Sheet to be used for the web page'))
menu.add_option(category_name, "css", css)
def __add_content_options(self, menu):
"""
Options on the "Content Options" tab.
"""
category_name = _("Content Options")
# set to today's date for use in menu, etc.
today = date.Today()
self.__multiyear = BooleanOption(_('Create multiple year calendars'), False)
self.__multiyear.set_help(_('Whether to create Multiple year calendars or not.'))
menu.add_option(category_name, 'multiyear', self.__multiyear)
self.__multiyear.connect('value-changed', self.__multiyear_changed)
self.__start_year = NumberOption(_('Start Year for the Calendar(s)'), today.get_year(),
1900, 3000)
self.__start_year.set_help(_('Enter the starting year for the calendars '
'between 1900 - 3000'))
menu.add_option(category_name, 'start_year', self.__start_year)
self.__end_year = NumberOption(_('End Year for the Calendar(s)'), today.get_year(),
1900, 3000)
self.__end_year.set_help(_('Enter the ending year for the calendars '
'between 1900 - 3000.'))
menu.add_option(category_name, 'end_year', self.__end_year)
self.__multiyear_changed()
country = EnumeratedListOption(_('Country for holidays'), 0 )
holiday_table = libholiday.HolidayTable()
countries = holiday_table.get_countries()
countries.sort()
if (len(countries) == 0 or
(len(countries) > 0 and countries[0] != '')):
countries.insert(0, '')
count = 0
for c in countries:
country.add_item(count, c)
count += 1
country.set_help(_("Holidays will be included for the selected "
"country"))
menu.add_option(category_name, "country", country)
maiden_name = EnumeratedListOption(_("Birthday surname"), "own")
maiden_name.add_item('spouse_first', _("Wives use husband's surname "
"(from first family listed)"))
maiden_name.add_item('spouse_last', _("Wives use husband's surname "
"(from last family listed)"))
maiden_name.add_item("own", _("Wives use their own surname"))
maiden_name.set_help(_("Select married women's displayed surname"))
menu.add_option(category_name, "maiden_name", maiden_name)
# Default selection ????
start_dow = EnumeratedListOption(_("First day of week"), 1)
for count in range(1, 8):
start_dow.add_item(count, GrampsLocale.long_days[count].capitalize())
start_dow.set_help(_("Select the first day of the week for the calendar"))
menu.add_option(category_name, "start_dow", start_dow)
home_link = StringOption(_('Home link'), '../../NAVWEB/index.html')
home_link.set_help(_("The link to be included to direct the user to "
"the main page of the web site"))
menu.add_option(category_name, "home_link", home_link)
alive = BooleanOption(_("Include only living people"), True)
alive.set_help(_("Include only living people in the calendar"))
menu.add_option(category_name, "alive", alive)
birthdays = BooleanOption(_("Include birthdays"), True)
birthdays.set_help(_("Include birthdays in the calendar"))
menu.add_option(category_name, "birthdays", birthdays)
anniversaries = BooleanOption(_("Include anniversaries"), True)
anniversaries.set_help(_("Include anniversaries in the calendar"))
menu.add_option(category_name, "anniversaries", anniversaries)
def __add_notes_options(self, menu):
"""
Options on the "Months Notes" tabs.
"""
category_name = _("Jan - Jun Notes")
note_jan = NoteOption(_('January Note'))
note_jan.set_help(_("The note for the month of January"))
menu.add_option(category_name, "note_jan", note_jan)
note_feb = NoteOption(_('February Note'))
note_feb.set_help(_("The note for the month of February"))
menu.add_option(category_name, "note_feb", note_feb)
note_mar = NoteOption(_('March Note'))
note_mar.set_help(_("The note for the month of March"))
menu.add_option(category_name, "note_mar", note_mar)
note_apr = NoteOption(_('April Note'))
note_apr.set_help(_("The note for the month of April"))
menu.add_option(category_name, "note_apr", note_apr)
note_may = NoteOption(_('May Note'))
note_may.set_help(_("The note for the month of May"))
menu.add_option(category_name, "note_may", note_may)
note_jun = NoteOption(_('June Note'))
note_jun.set_help(_("The note for the month of June"))
menu.add_option(category_name, "note_jun", note_jun)
category_name = _("Jul - Dec Notes")
note_jul = NoteOption(_('July Note'))
note_jul.set_help(_("The note for the month of July"))
menu.add_option(category_name, "note_jul", note_jul)
note_aug = NoteOption(_('August Note'))
note_aug.set_help(_("The note for the month of August"))
menu.add_option(category_name, "note_aug", note_aug)
note_sep = NoteOption(_('September Note'))
note_sep.set_help(_("The note for the month of September"))
menu.add_option(category_name, "note_sep", note_sep)
note_oct = NoteOption(_('October Note'))
note_oct.set_help(_("The note for the month of October"))
menu.add_option(category_name, "note_oct", note_oct)
note_nov = NoteOption(_('November Note'))
note_nov.set_help(_("The note for the month of November"))
menu.add_option(category_name, "note_nov", note_nov)
note_dec = NoteOption(_('December Note'))
note_dec.set_help(_("The note for the month of December"))
menu.add_option(category_name, "note_dec", note_dec)
def __add_advanced_options(self, menu):
"""
Options for the advanced menu
"""
category_name = _('Advanced Options')
encoding = EnumeratedListOption(_('Character set encoding'), _CHARACTER_SETS[0][1])
for eopt in _CHARACTER_SETS:
encoding.add_item(eopt[1], eopt[0])
encoding.set_help( _('The encoding to be used for the web files'))
menu.add_option(category_name, "encoding", encoding)
fullyear = BooleanOption(_('Create "Year At A Glance" Calendar'), False)
fullyear.set_help(_('Whether to create A one-page mini calendar '
'with dates highlighted'))
menu.add_option(category_name, 'fullyear', fullyear)
makeoneday = BooleanOption(_('Create one day event pages for'
' Year At A Glance calendar'), False)
makeoneday.set_help(_('Whether to create one day pages or not'))
menu.add_option(category_name, 'makeoneday', makeoneday)
self.__links = BooleanOption(_('Link to Narrated Web Report'), False)
self.__links.set_help(_('Whether to link data to web report or not'))
menu.add_option(category_name, 'link_to_narweb', self.__links)
self.__links.connect('value-changed', self.__links_changed)
self.__prefix = StringOption(_('Link prefix'), "../../NAVWEB/")
self.__prefix.set_help(_("A Prefix on the links to take you to "
"Narrated Web Report"))
menu.add_option(category_name, "prefix", self.__prefix)
self.__links_changed()
def __update_filters(self):
"""
Update the filter list based on the selected person
"""
gid = self.__pid.get_value()
person = self.__db.get_person_from_gramps_id(gid)
filter_list = ReportUtils.get_person_filters(person, False)
self.__filter.set_filters(filter_list)
def __filter_changed(self):
"""
Handle filter change. If the filter is not specific to a person,
disable the person option
"""
filter_value = self.__filter.get_value()
if 1 <= filter_value <= 4:
# Filters 1, 2, 3 and 4 rely on the center person
self.__pid.set_available(True)
else:
# The rest don't
self.__pid.set_available(False)
def __multiyear_changed(self):
"""
Handles the ability to print multiple year calendars or not?
"""
self.__start_year.set_available(True)
if self.__multiyear.get_value():
self.__end_year.set_available(True)
else:
self.__end_year.set_available(False)
def __links_changed(self):
"""
Handle checkbox change.
"""
if self.__links.get_value():
self.__prefix.set_available(True)
else:
self.__prefix.set_available(False)
# ---------------------------------------------------------------------------------------
# Support Functions for this plugin
# ---------------------------------------------------------------------------------------
def _get_regular_surname(sex, name):
"""
Returns a name string built from the components of the Name instance.
"""
surname = name.get_surname()
prefix = name.get_surname_prefix()
if prefix:
surname = prefix + " " + surname
if sex is not Person.FEMALE:
suffix = name.get_suffix()
if suffix:
surname = surname + ", " + suffix
return surname
# Simple utility list to convert Gramps day-of-week numbering
# to calendar.firstweekday numbering
dow_gramps2iso = [ -1, calendar.SUNDAY, calendar.MONDAY, calendar.TUESDAY,
calendar.WEDNESDAY, calendar.THURSDAY, calendar.FRIDAY,
calendar.SATURDAY]
# define names for full and abbreviated month names in GrampsLocale
full_month_name = _dd.long_months
abbr_month_name = _dd.short_months
def get_full_month_name(month):
""" returns full or long month name """
return full_month_name[month]
def get_short_month_name(month):
""" return short or abbreviated month name """
return abbr_month_name[month]
def get_marriage_event(db, family):
"""
marriage_event will either be the marriage event or False
"""
marriage_event = False
for event_ref in family.get_event_ref_list():
event = db.get_event_from_handle(event_ref.ref)
if event.type.is_marriage:
marriage_event = event
elif event.type.is_divorce:
continue
# return the marriage event or False to it caller
return marriage_event
def get_first_day_of_month(year, month):
"""
Compute the first day to display for this month.
It can also be a day in the previous month.
"""
# first day of the month
current_date = datetime.date(year, month, 1)
# monthinfo is filled using standard Python library
# calendar.monthcalendar. It fills a list of 7-day-lists. The first day
# of the 7-day-list is determined by calendar.firstweekday.
monthinfo = calendar.monthcalendar(year, month)
current_ord = current_date.toordinal() - monthinfo[0].count(0)
return current_date, current_ord, monthinfo
def _has_webpage_extension(url):
"""
determine if a filename has an extension or not...
url = filename to be checked
"""
return any(url.endswith(ext) for ext in _WEB_EXT)
# ---------------------------------------------------------------------------------------
# Gets individual events for each day
# ---------------------------------------------------------------------------------------
def get_day_list(event_date, holiday_list, bday_anniv_list):
"""
Will fill day_list and return it to its caller: calendar_build()
holiday_list -- list of holidays for event_date
bday_anniv_list -- list of birthdays and anniversaries
for event_date
event_date -- date for this day_list
'day_list' - a combination of both dictionaries to be able
to create one day nyears, date, text, event --- are
necessary for figuring the age or years married for
each day being created...
"""
# initialize day_list
day_list = []
##################################################################
# birthday/ anniversary on this day
# Date.EMPTY signifies an incomplete date for an event. See add_day_item()
bday_anniv_list = [(t, e, d) for t, e, d in bday_anniv_list
if d != Date.EMPTY]
# number of years have to be at least zero
bday_anniv_list = [(t, e, d) for t, e, d in bday_anniv_list
if (event_date.get_year() - d.get_year()) >= 0]
# a holiday
# zero will force holidays to be first in list
nyears = 0
for text, event, date in holiday_list:
day_list.append((nyears, date, text, event))
# birthday and anniversary list
for text, event, date in bday_anniv_list:
# number of years married, ex: 10
nyears = (event_date.get_year() - date.get_year())
# number of years for birthday, ex: 10 years
age_str = event_date - date
age_str.format(precision = 1)
# a birthday
if event == 'Birthday':
txt_str = (text + ', <em>'
+ (_('%s old') % str(age_str) if nyears else _('birth'))
+ '</em>')
# an anniversary
elif event == "Anniversary":
if nyears == 0:
txt_str = _('%(couple)s, <em>wedding</em>') % {
'couple' : text}
else:
txt_str = (ngettext('%(couple)s, <em>%(years)d'
'</em> year anniversary',
'%(couple)s, <em>%(years)d'
'</em> year anniversary', nyears)
% {'couple' : text, 'years' : nyears})
txt_str = Html('span', txt_str, class_ = "yearsmarried")
day_list.append((nyears, date, txt_str, event))
# sort them based on number of years
# holidays will always be on top of event list
day_list.sort()
# return to its caller calendar_build()
return day_list