Back to index

moin  1.9.0~rc2
MonthCalendar.py
Go to the documentation of this file.
00001 """
00002     MoinMoin - MonthCalendar Macro
00003 
00004     You can use this macro to put a month's calendar page on a Wiki page.
00005 
00006     The days are links to Wiki pages following this naming convention:
00007     BasePageName/year-month-day
00008 
00009     @copyright: 2002-2009 MoinMoin:ThomasWaldmann
00010     @license: GNU GPL, see COPYING for details.
00011 
00012     Revisions:
00013     * first revision without a number (=1.0):
00014         * was only online for a few hours and then replaced by 1.1
00015     * 1.1:
00016         * changed name to MonthCalendar to avoid conflict with "calendar" under case-insensitive OSes like Win32
00017         * days as subpages
00018         * basepage argument
00019         * change order of year/month argument
00020         * browsing links to prev/next month/year
00021             * current limitation: you can only browse one calendar on the same
00022               page/url, if you try to browse another calendar on the same page,
00023               the first one jumps back to its original display
00024     * show basepage in calendar header if basepage<>currentpage
00025     * 1.2:
00026         * minor fixes in argument parsing
00027         * cosmetic fix for netscape, other cosmetic changes, changed css
00028         * i18n support (weekday short names)
00029     * 1.3:
00030         * fixes to run with MoinMoin 0.11, thanks to JuergenHermann
00031         * fix: withspace before "," allowed in argument list
00032         * BasePage in calendar header (if present) is a link now
00033         * more i18n
00034         * HTML cleanup, generating code avoids bracketing errors
00035         * colour cosmetics
00036     * 1.4:
00037         * new parameter for enabling fixed height of 6 "calendar weeks",
00038           if you want to show a whole year's calendar, this just looks
00039           better than having some months with 4, some with 5 and some with 6.
00040         * group calendaring functions:
00041           * you can give mutliple BasePages UserName1*UserName2*UserName3
00042           * first BasePage is considered "your" Basepage,
00043             used days are bright red
00044           * 2nd and all other BasePages are considered "others" BasePages
00045             and lead to an increasing green component the more "used" days
00046             the others have. So white gets greener and red gets more yellowish.
00047           * in the head part of the calendar, you can click on each name
00048             to get to the Page of the same name
00049           * colouring of my and others BasePage is done in a way to show
00050             the colouring used in the calendar:
00051           * others use green colouring (increasingly green if multiply used)
00052           * I use red colouring, which gets more and more yellowish as
00053             the day is used by more and more others, too
00054     * 1.5:
00055         * fixed username colouring when using a BasePage
00056         * fixed navigation header of MonthCalendar not to get broken into
00057           multiple lines
00058         * fixed SubPage handling (please do not use relative SubPages like
00059           /SubPage yet. Use MyName/SubPage.)
00060     * 1.6:
00061         * syntactic cleanup
00062         * removed i18n compatibility for moin<1.1 or cvs<2003-06-10
00063         * integrated Scott Chapman's changes:
00064             * Made it configurable for Sunday or Monday as the first day of the week.
00065               Search for "change here".
00066             * Made it so that today is not only set to a seperate css style, but also boldfaced.
00067               Some browsers don't show the other css style (Netscape).
00068             * Made it so weekend dates have different color.
00069     * 1.7:
00070         * added request parameter where needed
00071     * 1.8:
00072         * some fixes for moin 1.2 (use this version ONLY if you run moin 1.2, too):
00073             * .value backtrace fixed when selecting next/prev month/year
00074             * request param added to macro function
00075     * 1.9:
00076         * adapted to moin 1.3
00077     * 2.0:
00078         * integrated into moin 1.3
00079         * added some nice JS (thanks to Klaus Knopper) to show nice mouseovers
00080           showing a preview of the day page linked (use first level headlines
00081           to make entries)
00082         * merged "common navigation" change of OliverGraf
00083         * merged AnnualMonthlyCalendar change of JonathanDietrich
00084     * 2.1:
00085         * fixed CSS for IE users
00086         * fix javascript for IE4
00087         * do a correct calculation of "today" using user's timezone
00088     * 2.2:
00089         * added template argument for specifying an edit template for new pages
00090     * 2.3:
00091         * adapted to moin 1.7 new macro parameter parsing
00092 
00093     Usage:
00094         <<MonthCalendar(BasePage,year,month,monthoffset,monthoffset2,height6,anniversary,template)>>
00095 
00096         each parameter can be empty and then defaults to currentpage or currentdate or monthoffset=0
00097 
00098     Samples (paste that to one of your pages for a first try):
00099 
00100 Calendar of current month for current page:
00101 <<MonthCalendar>>
00102 
00103 Calendar of last month:
00104 <<MonthCalendar(,,,-1)>>
00105 
00106 Calendar of next month:
00107 <<MonthCalendar(,,,+1)>>
00108 
00109 Calendar of Page SampleUser, this years december:
00110 <<MonthCalendar(SampleUser,,12)>>
00111 
00112 Calendar of current Page, this years december:
00113 <<MonthCalendar(,,12)>>
00114 
00115 Calendar of December, 2001:
00116 <<MonthCalendar(,2001,12)>>
00117 
00118 Calendar of the month two months after December, 2001
00119 (maybe doesn't make much sense, but is possible)
00120 <<MonthCalendar(,2001,12,+2)>>
00121 
00122 Calendar of year 2002 (every month padded to height of 6):
00123 ||||||Year 2002||
00124 ||<<MonthCalendar(,2002,1,,,1)>>||<<MonthCalendar(,2002,2,,,1)>>||<<MonthCalendar(,2002,3,,,1)>>||
00125 ||<<MonthCalendar(,2002,4,,,1)>>||<<MonthCalendar(,2002,5,,,1)>>||<<MonthCalendar(,2002,6,,,1)>>||
00126 ||<<MonthCalendar(,2002,7,,,1)>>||<<MonthCalendar(,2002,8,,,1)>>||<<MonthCalendar(,2002,9,,,1)>>||
00127 ||<<MonthCalendar(,2002,10,,,1)>>||<<MonthCalendar(,2002,11,,,1)>>||<<MonthCalendar(,2002,12,,,1)>>||
00128 
00129 Current calendar of me, also showing entries of A and B:
00130 <<MonthCalendar(MyPage*TestUserA*TestUserB)>>
00131 
00132 SubPage calendars:
00133 <<MonthCalendar(MyName/CalPrivate)>>
00134 <<MonthCalendar(MyName/CalBusiness)>>
00135 <<MonthCalendar(MyName/CalBusiness*MyName/CalPrivate)>>
00136 
00137 
00138 Anniversary Calendars: (no year data)
00139 <<MonthCalendar(Yearly,,,+1,,6,1)>>
00140 
00141 This creates calendars of the format Yearly/MM-DD
00142 By leaving out the year, you can set birthdays, and anniversaries in this
00143 calendar and not have to re-enter each year.
00144 
00145 This creates a calendar which uses MonthCalendarTemplate for directly editing
00146 nonexisting day pages:
00147 <<MonthCalendar(,,,,,,,MonthCalendarTemplate)>>
00148 """
00149 
00150 Dependencies = ['namespace', 'time', ]
00151 
00152 import re, calendar, time
00153 
00154 from MoinMoin import wikiutil
00155 from MoinMoin.Page import Page
00156 
00157 # The following line sets the calendar to have either Sunday or Monday as
00158 # the first day of the week. Only SUNDAY or MONDAY (case sensitive) are
00159 # valid here.  All other values will not make good calendars.
00160 # If set to Sunday, the calendar is displayed at "March 2003" vs. "2003 / 3" also.
00161 # XXX change here ----------------vvvvvv
00162 calendar.setfirstweekday(calendar.MONDAY)
00163 
00164 def cliprgb(r, g, b):
00165     """ clip r,g,b values into range 0..254 """
00166     def clip(x):
00167         """ clip x value into range 0..254 """
00168         if x < 0:
00169             x = 0
00170         elif x > 254:
00171             x = 254
00172         return x
00173     return clip(r), clip(g), clip(b)
00174 
00175 def yearmonthplusoffset(year, month, offset):
00176     """ calculate new year/month from year/month and offset """
00177     month += offset
00178     # handle offset and under/overflows - quick and dirty, yes!
00179     while month < 1:
00180         month += 12
00181         year -= 1
00182     while month > 12:
00183         month -= 12
00184         year += 1
00185     return year, month
00186 
00187 def parseargs(request, args, defpagename, defyear, defmonth, defoffset, defoffset2, defheight6, defanniversary, deftemplate):
00188     """ parse macro arguments """
00189     args = wikiutil.parse_quoted_separated(args, name_value=False)
00190     args += [None] * 8 # fill up with None to trigger defaults
00191     parmpagename, parmyear, parmmonth, parmoffset, parmoffset2, parmheight6, parmanniversary, parmtemplate = args[:8]
00192     parmpagename = wikiutil.get_unicode(request, parmpagename, 'pagename', defpagename)
00193     parmyear = wikiutil.get_int(request, parmyear, 'year', defyear)
00194     parmmonth = wikiutil.get_int(request, parmmonth, 'month', defmonth)
00195     parmoffset = wikiutil.get_int(request, parmoffset, 'offset', defoffset)
00196     parmoffset2 = wikiutil.get_int(request, parmoffset2, 'offset2', defoffset2)
00197     parmheight6 = wikiutil.get_bool(request, parmheight6, 'height6', defheight6)
00198     parmanniversary = wikiutil.get_bool(request, parmanniversary, 'anniversary', defanniversary)
00199     parmtemplate = wikiutil.get_unicode(request, parmtemplate, 'template', deftemplate)
00200 
00201     # multiple pagenames separated by "*" - split into list of pagenames
00202     parmpagename = re.split(r'\*', parmpagename)
00203 
00204     return parmpagename, parmyear, parmmonth, parmoffset, parmoffset2, parmheight6, parmanniversary, parmtemplate
00205 
00206 def execute(macro, text):
00207     request = macro.request
00208     formatter = macro.formatter
00209     _ = request.getText
00210 
00211     # return immediately if getting links for the current page
00212     if request.mode_getpagelinks:
00213         return ''
00214 
00215     currentyear, currentmonth, currentday, h, m, s, wd, yd, ds = request.user.getTime(time.time())
00216     thispage = formatter.page.page_name
00217     # does the url have calendar params (= somebody has clicked on prev/next links in calendar) ?
00218     if 'calparms' in macro.request.args:
00219         has_calparms = 1 # yes!
00220         text2 = macro.request.args['calparms']
00221         cparmpagename, cparmyear, cparmmonth, cparmoffset, cparmoffset2, cparmheight6, cparmanniversary, cparmtemplate = \
00222             parseargs(request, text2, thispage, currentyear, currentmonth, 0, 0, False, False, u'')
00223         # Note: cparmheight6 and cparmanniversary are not used, they are just there
00224         # to have a consistent parameter string in calparms and macro args
00225     else:
00226         has_calparms = 0
00227 
00228     if text is None: # macro call without parameters
00229         text = u''
00230 
00231     # parse and check arguments
00232     parmpagename, parmyear, parmmonth, parmoffset, parmoffset2, parmheight6, anniversary, parmtemplate = \
00233         parseargs(request, text, thispage, currentyear, currentmonth, 0, 0, False, False, u'')
00234 
00235     # does url have calendar params and is THIS the right calendar to modify (we can have multiple
00236     # calendars on the same page)?
00237     #if has_calparms and (cparmpagename,cparmyear,cparmmonth,cparmoffset) == (parmpagename,parmyear,parmmonth,parmoffset):
00238 
00239     # move all calendars when using the navigation:
00240     if has_calparms and cparmpagename == parmpagename:
00241         year, month = yearmonthplusoffset(parmyear, parmmonth, parmoffset + cparmoffset2)
00242         parmoffset2 = cparmoffset2
00243         parmtemplate = cparmtemplate
00244     else:
00245         year, month = yearmonthplusoffset(parmyear, parmmonth, parmoffset)
00246 
00247     if request.isSpiderAgent and abs(currentyear - year) > 1:
00248         return '' # this is a bot and it didn't follow the rules (see below)
00249     if currentyear == year:
00250         attrs = {}
00251     else:
00252         attrs = {'rel': 'nofollow' } # otherwise even well-behaved bots will index forever
00253 
00254     # get the calendar
00255     monthcal = calendar.monthcalendar(year, month)
00256 
00257     # european / US differences
00258     months = ('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December')
00259     # Set things up for Monday or Sunday as the first day of the week
00260     if calendar.firstweekday() == calendar.MONDAY:
00261         wkend = (5, 6)
00262         wkdays = ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun')
00263     if calendar.firstweekday() == calendar.SUNDAY:
00264         wkend = (0, 6)
00265         wkdays = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat')
00266 
00267     colorstep = 85
00268     p = Page(request, thispage)
00269     qpagenames = '*'.join([wikiutil.quoteWikinameURL(pn) for pn in parmpagename])
00270     qtemplate = wikiutil.quoteWikinameURL(parmtemplate)
00271     querystr = "calparms=%%s,%d,%d,%d,%%d,,,%%s" % (parmyear, parmmonth, parmoffset)
00272     prevlink = p.url(request, querystr % (qpagenames, parmoffset2 - 1, qtemplate))
00273     nextlink = p.url(request, querystr % (qpagenames, parmoffset2 + 1, qtemplate))
00274     prevylink = p.url(request, querystr % (qpagenames, parmoffset2 - 12, qtemplate))
00275     nextylink = p.url(request, querystr % (qpagenames, parmoffset2 + 12, qtemplate))
00276 
00277     prevmonth = formatter.url(1, prevlink, 'cal-link', **attrs) + '&lt;' + formatter.url(0)
00278     nextmonth = formatter.url(1, nextlink, 'cal-link', **attrs) + '&gt;' + formatter.url(0)
00279     prevyear = formatter.url(1, prevylink, 'cal-link', **attrs) + '&lt;&lt;' + formatter.url(0)
00280     nextyear = formatter.url(1, nextylink, 'cal-link', **attrs) + '&gt;&gt;' + formatter.url(0)
00281 
00282     if parmpagename != [thispage]:
00283         pagelinks = ''
00284         r, g, b = (255, 0, 0)
00285         l = len(parmpagename[0])
00286         steps = len(parmpagename)
00287         maxsteps = (255 / colorstep)
00288         if steps > maxsteps:
00289             steps = maxsteps
00290         chstep = int(l / steps)
00291         st = 0
00292         while st < l:
00293             ch = parmpagename[0][st:st+chstep]
00294             r, g, b = cliprgb(r, g, b)
00295             link = Page(request, parmpagename[0]).link_to(request, ch,
00296                         rel='nofollow',
00297                         style='background-color:#%02x%02x%02x;color:#000000;text-decoration:none' % (r, g, b))
00298             pagelinks = pagelinks + link
00299             r, g, b = (r, g+colorstep, b)
00300             st = st + chstep
00301         r, g, b = (255-colorstep, 255, 255-colorstep)
00302         for page in parmpagename[1:]:
00303             link = Page(request, page).link_to(request, page,
00304                         rel='nofollow',
00305                         style='background-color:#%02x%02x%02x;color:#000000;text-decoration:none' % (r, g, b))
00306             pagelinks = pagelinks + '*' + link
00307         showpagename = '   %s<BR>\n' % pagelinks
00308     else:
00309         showpagename = ''
00310     if calendar.firstweekday() == calendar.SUNDAY:
00311         resth1 = '  <th colspan="7" class="cal-header">\n' \
00312                  '%s' \
00313                  '   %s&nbsp;%s&nbsp;<b>&nbsp;%s&nbsp;%s</b>&nbsp;%s\n&nbsp;%s\n' \
00314                  '  </th>\n' % (showpagename, prevyear, prevmonth, months[month-1], str(year), nextmonth, nextyear)
00315     if calendar.firstweekday() == calendar.MONDAY:
00316         resth1 = '  <th colspan="7" class="cal-header">\n' \
00317                  '%s' \
00318                  '   %s&nbsp;%s&nbsp;<b>&nbsp;%s&nbsp;/&nbsp;%s</b>&nbsp;%s\n&nbsp;%s\n' \
00319                  '  </th>\n' % (showpagename, prevyear, prevmonth, str(year), month, nextmonth, nextyear)
00320     restr1 = ' <tr>\n%s </tr>\n' % resth1
00321 
00322     r7 = range(7)
00323     restd2 = []
00324     for wkday in r7:
00325         wday = _(wkdays[wkday])
00326         if wkday in wkend:
00327             cssday = "cal-weekend"
00328         else:
00329             cssday = "cal-workday"
00330         restd2.append('  <td class="%s">%s</td>\n' % (cssday, wday))
00331     restr2 = ' <tr>\n%s </tr>\n' % "".join(restd2)
00332 
00333     if parmheight6:
00334         while len(monthcal) < 6:
00335             monthcal = monthcal + [[0, 0, 0, 0, 0, 0, 0]]
00336 
00337     maketip_js = []
00338     restrn = []
00339     for week in monthcal:
00340         restdn = []
00341         for wkday in r7:
00342             day = week[wkday]
00343             if not day:
00344                 restdn.append('  <td class="cal-invalidday">&nbsp;</td>\n')
00345             else:
00346                 page = parmpagename[0]
00347                 if anniversary:
00348                     link = "%s/%02d-%02d" % (page, month, day)
00349                 else:
00350                     link = "%s/%4d-%02d-%02d" % (page, year, month, day)
00351                 daypage = Page(request, link)
00352                 if daypage.exists() and request.user.may.read(link):
00353                     csslink = "cal-usedday"
00354                     query = {}
00355                     r, g, b, u = (255, 0, 0, 1)
00356                     daycontent = daypage.get_raw_body()
00357                     header1_re = re.compile(r'^\s*=\s(.*)\s=$', re.MULTILINE) # re.UNICODE
00358                     titletext = []
00359                     for match in header1_re.finditer(daycontent):
00360                         if match:
00361                             title = match.group(1)
00362                             title = wikiutil.escape(title).replace("'", "\\'")
00363                             titletext.append(title)
00364                     link = wikiutil.escape(link).replace("'", "\\'")
00365                     tipname = link
00366                     tiptitle = link
00367                     tiptext = '<br>'.join(titletext)
00368                     maketip_js.append("maketip('%s','%s','%s');" % (tipname, tiptitle, tiptext))
00369                     attrs = {'onMouseOver': "tip('%s')" % tipname,
00370                              'onMouseOut': "untip()"}
00371                 else:
00372                     csslink = "cal-emptyday"
00373                     if parmtemplate:
00374                         query = {'action': 'edit', 'template': parmtemplate}
00375                     else:
00376                         query = {}
00377                     r, g, b, u = (255, 255, 255, 0)
00378                     if wkday in wkend:
00379                         csslink = "cal-weekend"
00380                     attrs = {'rel': 'nofollow'}
00381                 for otherpage in parmpagename[1:]:
00382                     otherlink = "%s/%4d-%02d-%02d" % (otherpage, year, month, day)
00383                     otherdaypage = Page(request, otherlink)
00384                     if otherdaypage.exists():
00385                         csslink = "cal-usedday"
00386                         if u == 0:
00387                             r, g, b = (r-colorstep, g, b-colorstep)
00388                         else:
00389                             r, g, b = (r, g+colorstep, b)
00390                 r, g, b = cliprgb(r, g, b)
00391                 style = 'background-color:#%02x%02x%02x' % (r, g, b)
00392                 fmtlink = formatter.url(1, daypage.url(request, query), csslink, **attrs) + str(day) + formatter.url(0)
00393                 if day == currentday and month == currentmonth and year == currentyear:
00394                     cssday = "cal-today"
00395                     fmtlink = "<b>%s</b>" % fmtlink # for browser with CSS probs
00396                 else:
00397                     cssday = "cal-nottoday"
00398                 restdn.append('  <td style="%s" class="%s">%s</td>\n' % (style, cssday, fmtlink))
00399         restrn.append(' <tr>\n%s </tr>\n' % "".join(restdn))
00400 
00401     restable = '<table border="2" cellspacing="2" cellpadding="2">\n<col width="14%%" span="7">%s%s%s</table>\n'
00402     restable = restable % (restr1, restr2, "".join(restrn))
00403 
00404     if maketip_js:
00405         tip_js = '''<script language="JavaScript" type="text/javascript">
00406 <!--
00407 %s
00408 // -->
00409 </script>
00410 ''' % '\n'.join(maketip_js)
00411     else:
00412         tip_js = ''
00413 
00414     result = """\
00415 <script type="text/javascript" src="%s/common/js/infobox.js"></script>
00416 <div id="%s" style="position:absolute; visibility:hidden; z-index:20; top:-999em; left:0px;"></div>
00417 %s%s
00418 """ % (request.cfg.url_prefix_static, formatter.make_id_unique('infodiv'), tip_js, restable)
00419     return formatter.rawHTML(result)
00420 
00421 # EOF
00422