|
21 | 21 |
|
22 | 22 | import calendar
|
23 | 23 | import csv
|
| 24 | +import logging |
24 | 25 | import os.path
|
25 | 26 | import re
|
26 | 27 | import textwrap
|
|
52 | 53 | except ImportError:
|
53 | 54 | from itertools import izip_longest as zip_longest
|
54 | 55 |
|
| 56 | +logger = logging.getLogger('roundup.template') |
55 | 57 |
|
56 | 58 | # List of schemes that are not rendered as links in rst and markdown.
|
57 | 59 | _disable_url_schemes = ['javascript', 'data']
|
@@ -329,9 +331,10 @@ def _find(self, name):
|
329 | 331 | src = os.path.join(realsrc, f)
|
330 | 332 | realpath = os.path.realpath(src)
|
331 | 333 | if not realpath.startswith(realsrc):
|
332 |
| - return # will raise invalid template |
| 334 | + return None # will raise invalid template |
333 | 335 | if os.path.exists(src):
|
334 | 336 | return (src, f)
|
| 337 | + return None |
335 | 338 |
|
336 | 339 | def check(self, name):
|
337 | 340 | return bool(self._find(name))
|
@@ -3569,6 +3572,7 @@ class TemplatingUtils:
|
3569 | 3572 | """
|
3570 | 3573 | def __init__(self, client):
|
3571 | 3574 | self.client = client
|
| 3575 | + self._ = self.client._ |
3572 | 3576 |
|
3573 | 3577 | def Batch(self, sequence, size, start, end=0, orphan=0, overlap=0):
|
3574 | 3578 | return Batch(self.client, sequence, size, start, end, orphan,
|
@@ -3619,7 +3623,7 @@ def html_calendar(self, request):
|
3619 | 3623 | display = request.form.getfirst("display", date_str)
|
3620 | 3624 | template = request.form.getfirst("@template", "calendar")
|
3621 | 3625 | form = request.form.getfirst("form")
|
3622 |
| - property = request.form.getfirst("property") |
| 3626 | + aproperty = request.form.getfirst("property") |
3623 | 3627 | curr_date = ""
|
3624 | 3628 | try:
|
3625 | 3629 | # date_str and display can be set to an invalid value
|
@@ -3656,7 +3660,7 @@ def html_calendar(self, request):
|
3656 | 3660 | res = []
|
3657 | 3661 |
|
3658 | 3662 | base_link = "%s?@template=%s&property=%s&form=%s&date=%s" % \
|
3659 |
| - (request.classname, template, property, form, curr_date) |
| 3663 | + (request.classname, template, aproperty, form, curr_date) |
3660 | 3664 |
|
3661 | 3665 | # navigation
|
3662 | 3666 | # month
|
@@ -3718,6 +3722,92 @@ def html_calendar(self, request):
|
3718 | 3722 | res.append('</table></td></tr></table>')
|
3719 | 3723 | return "\n".join(res)
|
3720 | 3724 |
|
| 3725 | + def readfile(self, name, optional=False): |
| 3726 | + """Used to inline a file from the template directory. |
| 3727 | +
|
| 3728 | + Used to inline file content into a template. If file |
| 3729 | + is not found in the template directory and |
| 3730 | + optional=False, it reports an error to the user via a |
| 3731 | + NoTemplate exception. If optional=True it returns an |
| 3732 | + empty string when it can't find the file. |
| 3733 | +
|
| 3734 | + Useful for inlining JavaScript kept in an external |
| 3735 | + file where you can use linters/minifiers and other |
| 3736 | + tools on it. |
| 3737 | +
|
| 3738 | + A TAL example: |
| 3739 | +
|
| 3740 | + <script tal:attributes="nonce request/client/client_nonce" |
| 3741 | + tal:content="python:utils.readfile('mylibrary.js')"></script> |
| 3742 | +
|
| 3743 | + This method does not expands any tokens in the file. |
| 3744 | + See expandfile() for replacing tokens in the file. |
| 3745 | + """ |
| 3746 | + file_result = self.client.instance.templates._find(name) |
| 3747 | + |
| 3748 | + if file_result is None: |
| 3749 | + if optional: |
| 3750 | + return "" |
| 3751 | + template_name = self.client.selectTemplate( |
| 3752 | + self.client.classname, self.client.template) |
| 3753 | + raise NoTemplate(self._( |
| 3754 | + "Unable to read or expand file '%(name)s' " |
| 3755 | + "in template '%(template)s'.") % { |
| 3756 | + "name": name, 'template': template_name}) |
| 3757 | + |
| 3758 | + fullpath, name = file_result |
| 3759 | + with open(fullpath) as f: |
| 3760 | + contents = f.read() |
| 3761 | + return contents |
| 3762 | + |
| 3763 | + def expandfile(self, name, values=None, optional=False): |
| 3764 | + """Read a file and replace token placeholders. |
| 3765 | +
|
| 3766 | + Given a file name and a dict of tokens and |
| 3767 | + replacements, read the file from the tracker template |
| 3768 | + directory. Then replace all tokens of the form |
| 3769 | + '%(token_name)s' with the values in the dict. If the |
| 3770 | + values dict is set to None, it acts like |
| 3771 | + readfile(). In addition to values passed into the |
| 3772 | + method, the value for the tracker base directory taken |
| 3773 | + from TRACKER_WEB is available as the 'base' token. The |
| 3774 | + client_nonce used for Content Security Policy (CSP) is |
| 3775 | + available as 'client_nonce'. If a token is not in the |
| 3776 | + dict, an empty string is returned and an error log |
| 3777 | + message is logged. See readfile for an usage example. |
| 3778 | + """ |
| 3779 | + # readfile() raises NoTemplate if optional = false and |
| 3780 | + # the file is not found. Returns empty string if file not |
| 3781 | + # found and optional = true. File contents otherwise. |
| 3782 | + contents = self.readfile(name, optional=optional) |
| 3783 | + |
| 3784 | + if values is None or not contents: # nothing to expand |
| 3785 | + return contents |
| 3786 | + tokens = {'base': self.client.db.config.TRACKER_WEB, |
| 3787 | + 'client_nonce': self.client.client_nonce} |
| 3788 | + tokens.update(values) |
| 3789 | + try: |
| 3790 | + return contents % tokens |
| 3791 | + except KeyError as e: |
| 3792 | + template_name = self.client.selectTemplate( |
| 3793 | + self.client.classname, self.client.template) |
| 3794 | + fullpath, name = self.client.instance.templates._find(name) |
| 3795 | + logger.error( |
| 3796 | + "When running expandfile('%(fullpath)s') in " |
| 3797 | + "'%(template)s' there was no value for token: '%(token)s'.", |
| 3798 | + {'fullpath': fullpath, 'token': e.args[0], |
| 3799 | + 'template': template_name}) |
| 3800 | + return "" |
| 3801 | + except ValueError as e: |
| 3802 | + fullpath, name = self.client.instance.templates._find(name) |
| 3803 | + logger.error(self._( |
| 3804 | + "Found an incorrect token when expandfile applied " |
| 3805 | + "string subsitution on '%(fullpath)s'. " |
| 3806 | + "ValueError('%(issue)s') was raised. Check the format " |
| 3807 | + "of your named conversion specifiers."), |
| 3808 | + {'fullpath': fullpath, 'issue': e.args[0]}) |
| 3809 | + return "" |
| 3810 | + |
3721 | 3811 |
|
3722 | 3812 | class MissingValue(object):
|
3723 | 3813 | def __init__(self, description, **kwargs):
|
|
0 commit comments