From 8b7a4cc40e9b2f34da94efb75b158da762624015 Mon Sep 17 00:00:00 2001 From: Oz N Tiram Date: Tue, 6 Jun 2017 11:35:59 +0200 Subject: bpo-30095: Make CSS classes used by calendar.HTMLCalendar customizable (GH-1439) Several class attributes have been added to calendar.HTMLCalendar that allow customization of the CSS classes used in the resulting HTML. This can be done by subclasses HTMLCalendar and overwriting those class attributes (Patch by Oz Tiram). --- Doc/library/calendar.rst | 81 +++++++++++++++++++++++++++++++++- Doc/whatsnew/3.7.rst | 23 +++++++--- Lib/calendar.py | 36 +++++++++++++--- Lib/test/test_calendar.py | 108 +++++++++++++++++++++++++++++++++------------- Misc/ACKS | 1 + 5 files changed, 205 insertions(+), 44 deletions(-) diff --git a/Doc/library/calendar.rst b/Doc/library/calendar.rst index 41e9e28..a415b47 100644 --- a/Doc/library/calendar.rst +++ b/Doc/library/calendar.rst @@ -147,7 +147,7 @@ it's the base calendar for all computations. This class can be used to generate HTML calendars. - :class:`HTMLCalendar` instances have the following methods: + :class:`!HTMLCalendar` instances have the following methods: .. method:: formatmonth(theyear, themonth, withyear=True) @@ -171,6 +171,85 @@ it's the base calendar for all computations. output (defaulting to the system default encoding). + :class:`!HTMLCalendar` has the following attributes you can override to + customize the CSS classes used by the calendar: + + .. attribute:: cssclasses + + A list of CSS classes used for each weekday. The default class list is:: + + cssclasses = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"] + + more styles can be added for each day:: + + cssclasses = ["mon text-bold", "tue", "wed", "thu", "fri", "sat", "sun red"] + + Note that the length of this list must be seven items. + + + .. attribute:: cssclass_noday + + The CSS class for a weekday occurring in the previous or coming month. + + .. versionadded:: 3.7 + + + .. attribute:: cssclasses_weekday_head + + A list of CSS classes used for weekday names in the header row. + The default is the same as :attr:`cssclasses`. + + .. versionadded:: 3.7 + + + .. attribute:: cssclass_month_head + + The month's head CSS class (used by :meth:`formatmonthname`). + The default value is ``"month"``. + + .. versionadded:: 3.7 + + + .. attribute:: cssclass_month + + The CSS class for the whole month's table (used by :meth:`formatmonth`). + The default value is ``"month"``. + + .. versionadded:: 3.7 + + + .. attribute:: cssclass_year + + The CSS class for the whole year's table of tables (used by + :meth:`formatyear`). The default value is ``"year"``. + + .. versionadded:: 3.7 + + + .. attribute:: cssclass_year_head + + The CSS class for the table head for the whole year (used by + :meth:`formatyear`). The default value is ``"year"``. + + .. versionadded:: 3.7 + + + Note that although the naming for the above described class attributes is + singular (e.g. ``cssclass_month`` ``cssclass_noday``), one can replace the + single CSS class with a space separated list of CSS classes, for example:: + + "text-bold text-red" + + Here is an example how :class:`!HTMLCalendar` can be customized:: + + class CustomHTMLCal(calendar.HTMLCalendar): + cssclasses = [style + " text-nowrap" for style in + calendar.HTMLCalendar.cssclasses] + cssclass_month_head = "text-center month-head" + cssclass_month = "text-center month" + cssclass_year = "text-italic lead" + + .. class:: LocaleTextCalendar(firstweekday=0, locale=None) This subclass of :class:`TextCalendar` can be passed a locale name in the diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst index 761c85f..602db8c 100644 --- a/Doc/whatsnew/3.7.rst +++ b/Doc/whatsnew/3.7.rst @@ -103,13 +103,6 @@ New Modules Improved Modules ================ -cgi ---- - -:func:`~cgi.parse_multipart` returns the same results as -:class:`~FieldStorage` : for non-file fields, the value associated to a key -is a list of strings, not bytes. -(Contributed by Pierre Quentel in :issue:`29979`.) binascii -------- @@ -118,6 +111,22 @@ The :func:`~binascii.b2a_uu` function now accepts an optional *backtick* keyword argument. When it's true, zeros are represented by ``'`'`` instead of spaces. (Contributed by Xiang Zhang in :issue:`30103`.) + +calendar +-------- + +The :class:`~calendar.HTMLCalendar` has added new class attribute which ease the +customisation the CSS classes in the produced HTML calendar. +(Contributed by Oz Tiram in :issue:`30095`.) + +cgi +--- + +:func:`~cgi.parse_multipart` returns the same results as +:class:`~FieldStorage` : for non-file fields, the value associated to a key +is a list of strings, not bytes. +(Contributed by Pierre Quentel in :issue:`29979`.) + contextlib ---------- diff --git a/Lib/calendar.py b/Lib/calendar.py index 28ac56f..0218e2d 100644 --- a/Lib/calendar.py +++ b/Lib/calendar.py @@ -382,12 +382,31 @@ class HTMLCalendar(Calendar): # CSS classes for the day s cssclasses = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"] + # CSS classes for the day s + cssclasses_weekday_head = cssclasses + + # CSS class for the days before and after current month + cssclass_noday = "noday" + + # CSS class for the month's head + cssclass_month_head = "month" + + # CSS class for the month + cssclass_month = "month" + + # CSS class for the year's table head + cssclass_year_head = "year" + + # CSS class for the whole year table + cssclass_year = "year" + def formatday(self, day, weekday): """ Return a day as a table cell. """ if day == 0: - return ' ' # day outside month + # day outside month + return ' ' % self.cssclass_noday else: return '%d' % (self.cssclasses[weekday], day) @@ -402,7 +421,8 @@ class HTMLCalendar(Calendar): """ Return a weekday name as a table header. """ - return '%s' % (self.cssclasses[day], day_abbr[day]) + return '%s' % ( + self.cssclasses_weekday_head[day], day_abbr[day]) def formatweekheader(self): """ @@ -419,7 +439,8 @@ class HTMLCalendar(Calendar): s = '%s %s' % (month_name[themonth], theyear) else: s = '%s' % month_name[themonth] - return '%s' % s + return '%s' % ( + self.cssclass_month_head, s) def formatmonth(self, theyear, themonth, withyear=True): """ @@ -427,7 +448,8 @@ class HTMLCalendar(Calendar): """ v = [] a = v.append - a('') + a('
' % ( + self.cssclass_month)) a('\n') a(self.formatmonthname(theyear, themonth, withyear=withyear)) a('\n') @@ -447,9 +469,11 @@ class HTMLCalendar(Calendar): v = [] a = v.append width = max(width, 1) - a('
') + a('
' % + self.cssclass_year) a('\n') - a('' % (width, theyear)) + a('' % ( + width, self.cssclass_year_head, theyear)) for i in range(January, January+12, width): # months in this row months = range(i, min(i+width, 13)) diff --git a/Lib/test/test_calendar.py b/Lib/test/test_calendar.py index bd57653..c777f64 100644 --- a/Lib/test/test_calendar.py +++ b/Lib/test/test_calendar.py @@ -57,19 +57,22 @@ Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su 25 26 27 28 29 30 31 29 30 27 28 29 30 31 """ + +default_format = dict(year="year", month="month", encoding="ascii") + result_2004_html = """\ - + - +Calendar for 2004 -
%s
%s
-
2004
- +
January
+
2004
+ @@ -77,8 +80,8 @@ result_2004_html = """\
January
MonTueWedThuFriSatSun
   1234
567891011
19202122232425
262728293031 
-
- +
February
+ @@ -86,8 +89,8 @@ result_2004_html = """\
February
MonTueWedThuFriSatSun
      1
2345678
16171819202122
23242526272829
-
- +
March
+ @@ -95,8 +98,8 @@ result_2004_html = """\
March
MonTueWedThuFriSatSun
1234567
891011121314
22232425262728
293031    
-
- +
April
+ @@ -104,8 +107,8 @@ result_2004_html = """\
April
MonTueWedThuFriSatSun
   1234
567891011
19202122232425
2627282930  
-
- +
May
+ @@ -114,8 +117,8 @@ result_2004_html = """\
May
MonTueWedThuFriSatSun
     12
3456789
24252627282930
31      
-
- +
June
+ @@ -123,8 +126,8 @@ result_2004_html = """\
June
MonTueWedThuFriSatSun
 123456
78910111213
21222324252627
282930    
-
- +
July
+ @@ -132,8 +135,8 @@ result_2004_html = """\
July
MonTueWedThuFriSatSun
   1234
567891011
19202122232425
262728293031 
-
- +
August
+ @@ -142,8 +145,8 @@ result_2004_html = """\
August
MonTueWedThuFriSatSun
      1
2345678
23242526272829
3031     
-
- +
September
+ @@ -151,8 +154,8 @@ result_2004_html = """\
September
MonTueWedThuFriSatSun
  12345
6789101112
20212223242526
27282930   
-
- +
October
+ @@ -160,8 +163,8 @@ result_2004_html = """\
October
MonTueWedThuFriSatSun
    123
45678910
18192021222324
25262728293031
-
- +
November
+ @@ -169,8 +172,8 @@ result_2004_html = """\
November
MonTueWedThuFriSatSun
1234567
891011121314
22232425262728
2930     
-
- +
December
+ @@ -327,9 +330,12 @@ class OutputTestCase(unittest.TestCase): def check_htmlcalendar_encoding(self, req, res): cal = calendar.HTMLCalendar() + format_ = default_format.copy() + format_["encoding"] = req or 'utf-8' + output = cal.formatyearpage(2004, encoding=req) self.assertEqual( - cal.formatyearpage(2004, encoding=req), - (result_2004_html % {'e': res}).encode(res) + output, + result_2004_html.format(**format_).encode(res) ) def test_output(self): @@ -825,7 +831,7 @@ class CommandLineTestCase(unittest.TestCase): def test_html_output_year_encoding(self): stdout = self.run_ok('-t', 'html', '--encoding', 'ascii', '2004') self.assertEqual(stdout, - (result_2004_html % {'e': 'ascii'}).encode('ascii')) + result_2004_html.format(**default_format).encode('ascii')) def test_html_output_year_css(self): self.assertFailure('-t', 'html', '-c') @@ -844,5 +850,47 @@ class MiscTestCase(unittest.TestCase): support.check__all__(self, calendar, blacklist=blacklist) +class TestSubClassingCase(unittest.TestCase): + + def setUp(self): + + class CustomHTMLCal(calendar.HTMLCalendar): + cssclasses = [style + " text-nowrap" for style in + calendar.HTMLCalendar.cssclasses] + cssclasses_weekday_head = ["red", "blue", "green", "lilac", + "yellow", "orange", "pink"] + cssclass_month_head = "text-center month-head" + cssclass_month = "text-center month" + cssclass_year = "text-italic " + cssclass_year_head = "lead " + + self.cal = CustomHTMLCal() + + def test_formatmonthname(self): + self.assertIn('class="text-center month-head"', + self.cal.formatmonthname(2017, 5)) + + def test_formatmonth(self): + self.assertIn('class="text-center month"', + self.cal.formatmonth(2017, 5)) + + def test_formatweek(self): + weeks = self.cal.monthdays2calendar(2017, 5) + self.assertIn('class="wed text-nowrap"', self.cal.formatweek(weeks[0])) + + def test_formatweek_head(self): + header = self.cal.formatweekheader() + for color in self.cal.cssclasses_weekday_head: + self.assertIn('
December
MonTueWedThuFriSatSun
  12345
6789101112
' % color, header) + + def test_format_year(self): + self.assertIn( + ('' % + self.cal.cssclass_year), self.cal.formatyear(2017)) + + def test_format_year_head(self): + self.assertIn('' % ( + 3, self.cal.cssclass_year_head, 2017), self.cal.formatyear(2017)) + if __name__ == "__main__": unittest.main() diff --git a/Misc/ACKS b/Misc/ACKS index 3000bc3..85d2fc6 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1556,6 +1556,7 @@ Jeremy Thurgood Eric Tiedemann July Tikhonov Tracy Tims +Oz Tiram Oren Tirosh Tim Tisdall Jason Tishler -- cgit v0.12
%s