def render_text(text, autocompletes=None, comment=None, unwrap_p=False):
    # Render comment text into HTML.

    import re

    # Put @-mentions in bold.
    if autocompletes:
        text, _ = match_autocompletes(text, autocompletes,
                                      lambda text: "**" + text + "**")

    # Rewrite attachment:### URLs.
    if comment is not None:

        def get_attachment_url(attachment_id):
            try:
                return Attachment.objects.get(
                    id=attachment_id.group(1)).get_absolute_url()
            except:
                return "about:blank"

        text = re.sub("(?<=\()attachment:(\d+)(?=\))", get_attachment_url,
                      text)

    # Render to HTML as if CommonMark.
    import CommonMark
    parsed = CommonMark.Parser().parse(text)
    text = CommonMark.HtmlRenderer({"safe": True}).render(parsed)

    if unwrap_p:
        # If it's a single paragraph, unwrap it.
        text = re.sub(r"^<p>(.*)</p>$", r"\1", text)

    return text
Example #2
0
    def to_json(self, user=None):
        parser = CommonMark.Parser()
        renderer = CommonMark.HtmlRenderer()
        ast = parser.parse(self.event.abstract)
        abstract = renderer.render(ast)

        data = {
            'title': self.event.title,
            'event_slug': self.event.slug,
            'abstract': abstract,
            'from': self.when.lower.isoformat(),
            'to': self.when.upper.isoformat(),
            'url': str(self.event.get_absolute_url()),
            'id': self.id,
            'speakers': [
                { 'name': speaker.name
                , 'url': str(speaker.get_absolute_url())
                } for speaker in self.event.speakers.all()
            ],
            'bg-color': self.event.event_type.color,
            'fg-color': '#fff' if self.event.event_type.light_text else '#000',
            'event_type': self.event.event_type.slug,
            'location': self.location.slug,
            'location_icon': self.location.icon,
            'timeslots': self.timeslots,
        }

        if user and user.is_authenticated:
            is_favorited = user.favorites.filter(event_instance=self).exists()
            data['is_favorited'] = is_favorited

        return data
Example #3
0
 def _render(self, text):
     '''Render CommonMark with ettings taken in account'''
     parser = CommonMark.Parser()
     ast = parser.parse(text)
     renderer = HtmlRenderer(self)
     html = renderer.render(ast)
     return html
Example #4
0
    def parse(self, src):
        self.src = src
        self.recipe = Recipe(title=None)

        parser = CommonMark.Parser()
        ast = parser.parse(src)

        self.current = ast.first_child

        self._parse_title()
        self._parse_description()
        self._parse_tags_and_yields()

        if self.current is not None and self.current.t == 'thematic_break':
            self._next_node()
        else:
            # TODO this divider is required, but we might just continue anyways?
            raise RuntimeError("Invalid, expected divider before ingredients")

        self._parse_ingredients()

        if self.current is not None:
            if self.current.t == 'thematic_break':
                self._next_node()
            else:
                # TODO this divider is required, but we might just continue anyways?
                raise RuntimeError("Invalid, expected divider after ingredients")

        self.recipe.instructions = self._get_source_until()

        return self.recipe
def markdown(text, escape=True):
    parser = CommonMark.Parser()
    ast = parser.parse(text)

    renderer = CommonMark.HtmlRenderer()
    html = renderer.render(ast)
    return html
Example #6
0
def extract_last_changelog(path):
    with open(path, 'r') as fp:
        parser = CommonMark.Parser()
        content = fp.read()

    changelog = ast_to_changelog(parser.parse(content))

    if len(changelog.releases) == 0:
        raise Exception('No changelog releases')

    if len(changelog.releases) > 1:
        current = changelog.releases[0]
        previous = changelog.releases[1]

        with open(path, 'r') as fp:
            content = fp.read()

        pattern = r'\#\# {}(.*\n)([\n\S\s]*)\#\# {}'.format(re.escape(current.name), re.escape(previous.name))
        result = re.search(pattern, content, re.MULTILINE)
        return result.group(2).strip()
    elif len(changelog.releases) == 1:
        current = changelog.releases[0]

        with open(path, 'r') as fp:
            content = fp.read()

        pattern = r'\#\# {}(.*\n)([\n\S\s]*)'.format(re.escape(current.name))
        result = re.search(pattern, content, re.MULTILINE)
        return result.group(2).strip()
Example #7
0
def neomarkdown(markdown_content):
    parser = CommonMark.Parser()
    renderer = CommonMark.HtmlRenderer()
    ast = parser.parse(markdown_content)
    html = renderer.render(ast)
    # json = CommonMark.dumpJSON(ast)
    # CommonMark.dumpAST(ast)
    return html
Example #8
0
    def __init__(self, parser=None, style_name=None):
        if parser is None:
            parser = CommonMark.Parser()

        if style_name is None:
            style_name = 'native'

        self.parser = parser
        self.style_name = style_name
        self.list_level = -1
        self.counters = {}
        self.footnotes = []
Example #9
0
 def test_smart_dashes(self):
     md = 'a - b -- c --- d ---- e ----- f'
     EM = '\u2014'
     EN = '\u2013'
     expected_html = ('<p>' + 'a - ' + 'b ' + EN + ' ' + 'c ' + EM + ' ' +
                      'd ' + EN + EN + ' ' + 'e ' + EM + EN + ' ' +
                      'f</p>\n')
     parser = CommonMark.Parser(options=dict(smart=True))
     ast = parser.parse(md)
     renderer = CommonMark.HtmlRenderer()
     html = renderer.render(ast)
     self.assertEqual(html, expected_html)
Example #10
0
def page(name='teemo'):
    exodus_dir = os.path.dirname(os.path.realpath(__file__))
    page = 'pages/%s.md' % name
    path = os.path.join(exodus_dir, page)
    if path.startswith(exodus_dir) and os.path.exists(path):
        with open(str(path)) as p: 
            c = p.read()
            parser = CommonMark.Parser()
            ast = parser.parse(unicode(c, 'utf-8'))
            renderer = CommonMark.HtmlRenderer()
            html = renderer.render(ast)
            return render_template('page.html', page=html)
    else:
        return render_template('404.html'), 404
Example #11
0
    def handle(self, *args, **options):
        markdown_txt = options['input_file'].read_text(encoding='utf-8')
        parser = CommonMark.Parser()
        ast = parser.parse(markdown_txt)

        expression_date = options['expression_date'].date()
        work_id = options.get('work_id', options['input_file'].stem)
        expression_id = options.get('expression_id',
                                    expression_date.isoformat())

        work, _ = Work.objects.get_or_create(
            doc_type=options['doc_type'],
            doc_subtype=options['doc_subtype'],
            work_id=work_id,
        )
        Expression.objects.filter(work=work,
                                  expression_id=expression_id).delete()
        expression = Expression.objects.create(work=work,
                                               expression_id=expression_id,
                                               date=expression_date,
                                               author=options['author'])

        cursor = None
        for ast_node, entering in ast.walker():
            if entering:
                fn_name = f"enter_{ast_node.t}"
            else:
                fn_name = f"exit_{ast_node.t}"

            if hasattr(markdown_transforms, fn_name):
                cursor = getattr(markdown_transforms, fn_name)(cursor,
                                                               ast_node)
            elif fn_name not in markdown_transforms.skips:
                logger.warning("Don't know how to parse %s", fn_name)

        cursor.renumber()
        for node in cursor.walk():
            node.struct.expression = expression
            node.struct.save()

            for span in node.extra.get('spans', []):
                span.doc_struct = node.struct
                span.save()

        logger.info('Created %s DocStructs for %s/%s/%s/@%s',
                    cursor.subtree_size(), work.doc_type, work.doc_subtype,
                    work.work_id, expression.expression_id)
Example #12
0
def markdown(value):
    # Renders the string using CommonMark in safe mode, which blocks
    # raw HTML in the input and also some links using a blacklist,
    # plus a second pass filtering using a whitelist for allowed
    # tags and URL schemes.

    import CommonMark
    ast = CommonMark.Parser().parse(force_unicode(value))
    html = CommonMark.HtmlRenderer({'safe': True}).render(ast)

    import html5lib, urlparse

    def filter_url(url):
        try:
            urlp = urlparse.urlparse(url)
        except Exception as e:
            # invalid URL
            return None
        if urlp.scheme not in ("http", "https"):
            return None
        return url

    valid_tags = set(
        'strong em a code p h1 h2 h3 h4 h5 h6 pre br hr img ul ol li span blockquote'
        .split())
    valid_tags = set('{http://www.w3.org/1999/xhtml}' + tag
                     for tag in valid_tags)
    dom = html5lib.HTMLParser().parseFragment(html)
    for node in dom.iter():
        if node.tag not in valid_tags and node.tag != 'DOCUMENT_FRAGMENT':
            node.tag = '{http://www.w3.org/1999/xhtml}span'
        for name, val in node.attrib.items():
            if name.lower() in ("href", "src"):
                val = filter_url(val)
                if val is None:
                    node.attrib.pop(name)
                else:
                    node.set(name, val)
            else:
                # No other attributes are permitted.
                node.attrib.pop(name)
    html = html5lib.serialize(dom,
                              quote_attr_values="always",
                              omit_optional_tags=False,
                              alphabetical_attributes=True)

    return safestring.mark_safe(html)
Example #13
0
def run_tests():
    test_output = []

    parser = CommonMark.Parser()

    for test in get_tests():
        # Render as text and output.
        # Since we don't have reference output, we'll make our own and
        # just track if it changes.

        renderer = CommonMarkExtensions.plaintext.PlainTextRenderer()
        ast = parser.parse(test['markdown'])

        heading = "TEST (%s)" % test['section']
        print(heading)
        print("=" * len(heading))
        print()
        print("markdown:")
        print(test['markdown'])
        print("HTML:")
        print(test['html'])
        print("output:")

        try:
            output = renderer.render(ast)
        except CommonMarkExtensions.plaintext.RawHtmlNotAllowed:
            print("[raw HTML is not permitted in plain text output]\n")
        else:
            print(output)

        # Render as CommonMark, then re-parse it, render that to
        # HTML, and see if the HTML matches the reference HTML.
        cm = CommonMarkExtensions.plaintext.CommonMarkToCommonMarkRenderer(
        ).render(ast)
        ast = parser.parse(cm)
        html = CommonMark.render.html.HtmlRenderer().render(ast)
        if html != test['html']:
            # This is an error. Round-tripping didn't work.
            print("Round-tripping CommonMark failed. It generated:")
            print(cm)
Example #14
0
def main():
    parser = argparse.ArgumentParser(
        description="Process Markdown according to "
        "the CommonMark specification.")
    if sys.version_info < (3, 0):
        reload(sys)  # noqa
        sys.setdefaultencoding('utf-8')
    parser.add_argument('infile',
                        nargs="?",
                        type=argparse.FileType('r'),
                        default=sys.stdin,
                        help="Input Markdown file to parse, defaults to STDIN")
    parser.add_argument('-o',
                        nargs="?",
                        type=argparse.FileType('w'),
                        default=sys.stdout,
                        help="Output HTML/JSON file, defaults to STDOUT")
    parser.add_argument('-a', action="store_true", help="Print formatted AST")
    parser.add_argument('-aj', action="store_true", help="Output JSON AST")
    args = parser.parse_args()
    parser = CommonMark.Parser()
    f = args.infile
    o = args.o
    lines = []
    for line in f:
        lines.append(line)
    data = "".join(lines)
    ast = parser.parse(data)
    if not args.a and not args.aj:
        renderer = CommonMark.HtmlRenderer()
        o.write(renderer.render(ast))
        exit()
    if args.a:
        # print ast
        CommonMark.dumpAST(ast)
        exit()

    # o.write(ast.to_JSON())
    o.write(CommonMark.dumpJSON(ast))
    exit()
Example #15
0
def render_commonmark_template(template, context):
  # Render a CommonMark template to HTML.

  # Replace template tags with Unicode sentinels so that
  # the template tags do not impact CommonMark rendering.
  substitutions = []
  import re
  def replace(m):
      # Record the substitution.
      index = len(substitutions)
      substitutions.append(m.group(0))
      return "\uE000%d\uE001" % index # use Unicode private use area code points
  template = re.sub("{%.*?%}|{{.*?}}", replace, template)

  # Render the CommonMark.

  # Prevent CommonMark from mucking with our sentinels
  # in URLs though - it would otherwise add escaping.
  from CommonMark import inlines
  def urlencode_special(uri):
      import urllib.parse
      return "".join(
          urllib.parse.quote(c, safe="/@:+?=&()%#*,") # this is what CommonMark does
          if c not in "\uE000\uE001" else c # but keep our special codes
          for c in uri)
  inlines.normalize_uri = urlencode_special

  # Render.
  import CommonMark
  template = CommonMark.HtmlRenderer().render(CommonMark.Parser().parse(template))

  # Put the template tags back that we removed prior to running
  # the CommonMark renderer.
  def replace(m):
      return substitutions[int(m.group(1))]
  template = re.sub("\uE000(\d+)\uE001", replace, template)

  # And finally render the Django template.
  return Template(template).render(Context(context))
def main(where):
    parser = CommonMark.Parser()

    for root, subdirs, files in os.walk(where):
        for i in files:
            filename = root + '/' + i
            if not filename.endswith('.md'): continue

            ast = parser.parse(codecs.open(filename, encoding="utf8").read())
            for i, _ in ast.walker():

                if i.__dict__['t'] == "link":
                    url = i.__dict__['destination']
                    if url.startswith('http'):
                        check_external_url(url)
                        # local file
                    elif not url.startswith('/'):
                        raise Exception(
                            "a link to a local page must start with / " + url +
                            " in " + filename)
                    else:
                        linked_page = where + '/' + url.replace(".html", ".md")
                        if not os.path.exists(linked_page):
                            raise Exception("no such page " + linked_page)
Example #17
0
def unsafecommonmark(value):
    """Returns HTML given some CommonMark Markdown. Cleans HTML from input using bleach, suitable for use with untrusted input."""
    parser = CommonMark.Parser()
    renderer = CommonMark.HtmlRenderer()
    ast = parser.parse(bleach.clean(value))
    return mark_safe(renderer.render(ast))
Example #18
0
def commonmark(value):
    """Returns HTML given some CommonMark Markdown. Does not clean HTML, not for use with untrusted input."""
    parser = CommonMark.Parser()
    renderer = CommonMark.HtmlRenderer()
    ast = parser.parse(value)
    return mark_safe(renderer.render(ast))
Example #19
0
def main():

    prolog = """
   <meta charset="utf-8">
   <link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.7.0/styles/default.min.css">
   <script src="http://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.7.0/highlight.min.js"></script>
   <script>hljs.initHighlightingOnLoad();</script>
   """
    links = [('stylesheet', 'text/css', 'default.css')]
    scripts = []
    known_types = {
        'css': ('stylesheet', 'text/css'),
        'js': ('script', 'text/javascript')
    }

    parser = argparse.ArgumentParser(
        description="CommonMark to HTML5 processor")
    parser.add_argument('infile',
                        nargs="?",
                        type=argparse.FileType('r'),
                        default=sys.stdin,
                        help="The Markdown file to parse (defaults to STDIN)")
    parser.add_argument(
        '-o',
        nargs="?",
        type=argparse.FileType('w'),
        default=sys.stdout,
        help="The output HTML file to produce (defaults to STDOUT)")
    parser.add_argument('--inline',
                        action='store_true',
                        default=False,
                        help="Inline resources via data uris")
    parser.add_argument('--toc',
                        action='store_true',
                        default=False,
                        help="Generate a table of contents")
    parser.add_argument('--autonumber-title',
                        action='store_true',
                        dest='autonumbertitle',
                        default=False,
                        help="Generate the title in the table of contents")
    parser.add_argument('--autonumber',
                        action='store_true',
                        dest='autonumber',
                        default=False,
                        help="Generate the title in the table of contents")
    parser.add_argument('--prolog',
                        type=argparse.FileType('r'),
                        dest='prologFile',
                        nargs="?",
                        help="A file to embed in the prolog (head).")
    parser.add_argument('--link',
                        action='append',
                        dest='links',
                        help="A link to add to the prolog (head)")
    parser.add_argument(
        '--copy-style',
        action='append',
        dest='copyStyle',
        help="Copy the default style from the package to the local destination"
    )
    parser.add_argument('--script',
                        action='append',
                        dest='scripts',
                        help="A script to add to the prolog (head)")
    args = parser.parse_args()

    if args.copyStyle is not None:
        for source in args.copyStyle:
            this_dir, this_filename = os.path.split(__file__)
            path = os.path.join(this_dir, source)
            copyfile(path, source)

    f = args.infile
    o = args.o
    s = io.StringIO()
    for line in f:
        s.write(line)
    parser = CommonMark.Parser()
    ast = parser.parse(s.getvalue())
    s.close()

    if args.prologFile is not None:
        s = io.StringIO()
        for line in args.prologFile:
            s.write(line)
        prolog = s.getvalue()
        s.close()

    if args.links is not None:
        links = []
        for link in args.links:
            if link[0] == '(':
                links.append(literal_eval(link))
            else:
                ext = link[link.rfind('.') + 1:]
                if ext in known_types:
                    info = known_types[ext]
                    links.append((info[0], info[1], link))
                else:
                    raise ValueError('Unknown extension: ' + ext)

    if args.scripts is not None:
        scripts = []
        for script in args.scripts:
            if script[0] == '(':
                scripts.append(literal_eval(script))
            else:
                ext = script[script.rfind('.') + 1:]
                if ext in known_types:
                    info = known_types[ext]
                    scripts.append((info[1], script))
                else:
                    raise ValueError('Unknown extension: ' + ext)

    headings = toc(ast, args.autonumber,
                   args.autonumbertitle) if args.toc else None

    renderer = HTML5Renderer()
    title = findTitle(ast)
    o.write('<html>\n')
    o.write('<head>\n')
    o.write('<title>{0}</title>\n'.format(title))

    o.write(prolog)

    for link in links:
        if args.inline:
            o.write('<link rel="{0}" type="{1}" href="{2}">\n'.format(
                link[0], link[1], dataURI(link[1], link[2])))
        else:
            o.write('<link rel="{0}" type="{1}" href="{2}">\n'.format(*link))

    for script in scripts:
        if args.inline:
            o.write('<script type="{0}" src="{1}"></script>\n'.format(
                link[0], dataURI(link[0], link[1])))
        else:
            o.write('<script type="{0}" src="{1}"></script>\n'.format(*script))

    o.write('</head>\n')
    o.write('<body>\n')
    o.write('<main>\n')

    o.write('<h1>{0}</h1>'.format(title))

    if headings is not None:
        o.write('<header class="toc">\n')
        o.write('<h1>Table of Contents</h1>\n')
        writeTOC(
            o, headings if len(headings) > 1 or args.autonumbertitle else
            headings[0][2], args.autonumber)
        o.write('</header>\n')

    o.write('<article>\n')
    o.write(renderer.render(ast))
    o.write('</article>\n')
    o.write('</main>\n')
    o.write('</body>\n')
    o.write('</html>\n')
Example #20
0
 def __init__(self, *args, **kwargs):
     super(CompileCommonMark, self).__init__(*args, **kwargs)
     if CommonMark is not None:
         self.parser = CommonMark.Parser()
         self.renderer = CommonMark.HtmlRenderer()
Example #21
0
 def setUp(self):
     self.parser = CommonMark.Parser()
     self.renderer = CommonMark.ReStructuredTextRenderer()
Example #22
0
def commonmark(value):
    parser = CommonMark.Parser()
    renderer = CommonMark.HtmlRenderer()
    ast = parser.parse(force_text(value))
    return mark_safe(force_text(renderer.render(ast)))
Example #23
0
def trustedcommonmark(value):
    """Returns HTML given some CommonMark Markdown. Also allows real HTML, so do not use this with untrusted input."""
    parser = CommonMark.Parser()
    renderer = CommonMark.HtmlRenderer()
    ast = parser.parse(value)
    return mark_safe(renderer.render(ast))
 def __init__(self):
     self.path = os.path.join(settings.BASE_DIR, 'NEWS.md')
     self.parser = CommonMark.Parser()
     self.renderer = CommonMark.HtmlRenderer()
     self.cookie_name = 'news_current'
def debug(filename):
    print "\n".join(
        str(x) for x in list(CommonMark.Parser().parse(
            codecs.open(filename, encoding="utf8").read()).walker()))
Example #26
0
def parse_changelog(path):
    with open(path, 'r') as fp:
        parser = CommonMark.Parser()
        ast = parser.parse(fp.read())
        return ast_to_changelog(ast)
Example #27
0
import CommonMark

parser = CommonMark.Parser()

with open("data/am_hang/gipfel.md", "rt") as f:
    t = f.read()
    ast = parser.parse(t)

for node, b in ast.walker():
    print(b, node.level, node)
#    print(node.pretty())
Example #28
0
 def create(self, options):
     self.parser = CommonMark.Parser()
     self.renderer = CommonMark.HtmlRenderer()
Example #29
0
def run():
    """
    Looks through the docs/ dir and parses each markdown document, looking for
    sections to update from Python docstrings. Looks for section headers in
    the format:

     - ### `ClassName()` class
     - ##### `.method_name()` method
     - ##### `.attribute_name` attribute
     - ### `function_name()` function

    The markdown content following these section headers up until the next
    section header will be replaced by new markdown generated from the Python
    docstrings of the associated source files.

    By default maps docs/{name}.md to {modulename}/{name}.py. Allows for
    custom mapping via the MD_SOURCE_MAP variable.
    """

    print('Updating API docs...')

    md_files = []
    for root, _, filenames in os.walk(os.path.join(package_root, 'docs')):
        for filename in filenames:
            if not filename.endswith('.md'):
                continue
            md_files.append(os.path.join(root, filename))

    parser = CommonMark.Parser()

    for md_file in md_files:
        md_file_relative = md_file[len(package_root) + 1:]
        if md_file_relative in MD_SOURCE_MAP:
            py_files = MD_SOURCE_MAP[md_file_relative]
            py_paths = [
                os.path.join(package_root, py_file) for py_file in py_files
            ]
        else:
            py_files = [os.path.basename(md_file).replace('.md', '.py')]
            py_paths = [os.path.join(package_root, package_name, py_files[0])]

            if not os.path.exists(py_paths[0]):
                continue

        with open(md_file, 'rb') as f:
            markdown = f.read().decode('utf-8')

        original_markdown = markdown
        md_lines = list(markdown.splitlines())
        md_ast = parser.parse(markdown)

        last_class = []
        last = {}
        sections = OrderedDict()
        find_sections(md_ast, sections, last, last_class,
                      markdown.count("\n") + 1)

        md_chunks = {}

        for index, py_file in enumerate(py_files):
            py_path = py_paths[index]

            with open(os.path.join(py_path), 'rb') as f:
                code = f.read().decode('utf-8')
                module_ast = ast.parse(code, filename=py_file)
                code_lines = list(code.splitlines())

            for node in ast.iter_child_nodes(module_ast):
                walk_ast(node, code_lines, sections, md_chunks)

        added_lines = 0

        def _replace_md(key, sections, md_chunk, md_lines, added_lines):
            start, end = sections[key]
            start -= 1
            start += added_lines
            end += added_lines
            new_lines = md_chunk.split('\n')
            added_lines += len(new_lines) - (end - start)

            # Ensure a newline above each class header
            if start > 0 and md_lines[start][0:4] == '### ' and md_lines[
                    start - 1][0:1] == '>':
                added_lines += 1
                new_lines.insert(0, '')

            md_lines[start:end] = new_lines
            return added_lines

        for key in sections:
            if key not in md_chunks:
                raise ValueError('No documentation found for %s' % key[1])
            added_lines = _replace_md(key, sections, md_chunks[key], md_lines,
                                      added_lines)

        markdown = '\n'.join(md_lines).strip() + '\n'

        if original_markdown != markdown:
            with open(md_file, 'wb') as f:
                f.write(markdown.encode('utf-8'))
Example #30
0
def show_question(request, task, answered, context, q, EncryptionProvider, set_ephemeral_encryption_cookies):
    # Always hide the fill-out-your-profile blurb on all question pages - it's
    # distracting from answering the question at hand. See task_view.
    request.suppress_prompt_banner = True

    # If this question cannot currently be answered (i.e. dependencies are unmet),
    # then redirect away from this page. If the user is allowed to use the authoring
    # tool, then allow seeing this question so they can edit all questions.
    authoring_tool_enabled = task.module.is_authoring_tool_enabled(request.user)
    is_answerable = (((q not in answered.unanswered) or (q in answered.can_answer)) and (q.key not in answered.was_imputed))
    if not is_answerable and not authoring_tool_enabled:
        return HttpResponseRedirect(task.get_absolute_url())

    # Is there a TaskAnswer for this yet?
    taskq = TaskAnswer.objects.filter(task=task, question=q).first()

    # Display requested question.

    # Is there an answer already? (If this question isn't answerable, i.e. if we're
    # only here because the user is using the authoring tool, then there is no
    # real answer to load.)
    answer = None
    if taskq and is_answerable:
        answer = taskq.get_current_answer()
        if answer and answer.cleared:
            # If the answer is cleared, treat as if it had not been answered.
            answer = None

    # For "module"-type questions, get the Module instance of the tasks that can
    # be an answer to this question, and get the existing Tasks that the user can
    # choose as an answer.
    answer_module = q.answer_type_module
    answer_tasks = []
    if answer_module:
        # The user can choose from any Task instances they have read permission on
        # and that are of the correct Module type.
        answer_tasks = Task.get_all_tasks_readable_by(request.user, request.organization, recursive=True)\
            .filter(module=answer_module)

        # Annotate the instances with whether the user also has write permission.
        for t in answer_tasks:
            t.can_write = t.has_write_priv(request.user)

        # Sort the instances:
        #  first: the current answer, if any
        #  then: tasks defined in the same project as this task
        #  later: tasks defined in projects in the same folder as this task's project
        #  last: everything else by reverse update date
        now = timezone.now()
        current_answer = answer.answered_by_task.first() if answer else None
        answer_tasks = sorted(answer_tasks, key = lambda t : (
            not (t == current_answer),
            not (t.project == task.project),
            not (set(t.project.contained_in_folders.all()) & set(task.project.contained_in_folders.all())),
            now-t.updated,
            ))

    # Add instrumentation event.
    # How many times has this question been shown?
    i_prev_view = InstrumentationEvent.objects\
        .filter(user=request.user, event_type="task-question-show", task=task, question=q)\
        .order_by('-event_time')\
        .first()
    # Save.
    InstrumentationEvent.objects.create(
        user=request.user,
        event_type="task-question-show",
        event_value=(i_prev_view.event_value+1) if i_prev_view else 1,
        module=task.module,
        question=q,
        project=task.project,
        task=task,
        answer=taskq,
    )

    # Indicate for the InstrumentQuestionPageLoadTimes middleware that this is
    # a question page load.
    request._instrument_page_load = {
        "event_type": "task-question-request-duration",
        "module": task.module,
        "question": q,
        "project": task.project,
        "task": task,
        "answer": taskq,
    }

    # Construct the page.
    def render_markdown_field(field, output_format, **kwargs):
        template = q.spec.get(field)
        if not template:
            return None
        if not isinstance(template, str):
            raise ValueError("%s question %s %s is not a string" % (repr(q.module), q.key, field))
        return module_logic.render_content({
                "template": template,
                "format": "markdown",
            },
            answered,
            output_format,
            "%s question %s %s" % (repr(q.module), q.key, field),
            **kwargs
        )

    # Get any existing answer for this question.
    existing_answer = None
    if answer:
        existing_answer = answer.get_value(decryption_provider=EncryptionProvider())

        # For longtext questions, because the WYSIWYG editor is initialized with HTML,
        # render the value as HTML.
        if existing_answer and q.spec["type"] == "longtext":
            import CommonMark
            existing_answer = CommonMark.HtmlRenderer().render(CommonMark.Parser().parse(existing_answer))

    # What's the title/h1 of the page and the rest of the prompt? Render the
    # prompt field. If it starts with a paragraph, turn that paragraph into
    # the title.
    title = q.spec["title"]
    prompt = render_markdown_field("prompt", "html")
    m = re.match(r"^<p>([\w\W]*?)</p>\s*", prompt)
    if m:
        title = m.group(1)
        prompt = prompt[m.end():]

    # Get a default answer for this question. Render Jinja2 template, but don't turn
    # Markdown into HTML for plain text fields. For longtext fields, turn it into
    # HTML because the WYSIWYG editor is initialized with HTML.
    default_answer = render_markdown_field("default",
        "text" if q.spec["type"] != "longtext" else "html",
        demote_headings=False)

    context.update({
        "header_col_active": "start" if (len(answered.as_dict()) == 0 and q.spec["type"] == "interstitial") else "questions",
        "q": q,
        "title": title,
        "prompt": prompt,
        "placeholder_answer": render_markdown_field("placeholder", "text") or "", # Render Jinja2 template but don't turn Markdown into HTML.
        "reference_text": render_markdown_field("reference_text", "html"),
        "history": taskq.get_history() if taskq else None,
        "answer_obj": answer,
        "answer": existing_answer,
        "default_answer": default_answer,
        "review_choices": [(0, 'Not Reviewed'), (1, 'Reviewed'), (2, 'Approved')],
        "discussion": Discussion.get_for(request.organization, taskq) if taskq else None,
        "show_discussion_members_count": True,

        "answer_module": answer_module,
        "answer_tasks": answer_tasks,
        "answer_tasks_show_user": len([ t for t in answer_tasks if t.editor != request.user ]) > 0,

        "context": module_logic.get_question_context(answered, q),

        # Helpers for showing date month, day, year dropdowns, with
        # localized strings and integer values. Default selections
        # are done in the template & client-side so that we can use
        # the client browser's timezone to determine the current date.
        "date_l8n": lambda : {
            "months": [
                (timezone.now().replace(2016,m,1).strftime("%B"), m)
                for m in range(1, 12+1)],
            "days": [
                d
                for d in range(1, 31+1)],
            "years": [
                y
                for y in reversed(range(timezone.now().year-100, timezone.now().year+101))],
        },

        "is_answerable": is_answerable, # only false if authoring tool is enabled, otherwise this page is not renderable
        "authoring_tool_enabled": authoring_tool_enabled,
    })
    return render(request, "question.html", context)