Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(#153): Add support for rendering content in markdown format #222

Merged
merged 1 commit into from
Oct 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions comment/conf/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,7 @@
COMMENT_ALLOW_BLOCKING_USERS = False
COMMENT_ALLOW_MODERATOR_TO_BLOCK = False
COMMENT_RESPONSE_FOR_BLOCKED_USER = 'You cannot perform this action at the moment! Contact the admin for more details'

COMMENT_ALLOW_MARKDOWN = False
COMMENT_MARKDOWN_EXTENSIONS = ['markdown.extensions.fenced_code']
COMMENT_MARKDOWN_EXTENSION_CONFIG = {}
3 changes: 2 additions & 1 deletion comment/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,6 @@ def __call__(self):
'is_translation_allowed': settings.COMMENT_ALLOW_TRANSLATION,
'is_subscription_allowed': settings.COMMENT_ALLOW_SUBSCRIPTION,
'is_blocking_allowed': settings.COMMENT_ALLOW_BLOCKING_USERS,
'oauth': self.is_oauth()
'oauth': self.is_oauth(),
'render_markdown': settings.COMMENT_ALLOW_MARKDOWN,
}
6 changes: 5 additions & 1 deletion comment/templates/comment/comments/comment_content.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@

<div id="{{ comment.urlhash }}" class="js-updated-comment {% if comment.has_flagged_state %}flagged-comment {% endif %}{% block content_wrapper_cls %}{% if has_valid_profile %}col-9 col-md-10{% else %}co-11 mx-3{% endif %}{% endblock content_wrapper_cls %}" >
{% block comment_content %}
{% render_content comment %}
{% if render_markdown %}
{% render_content comment markdown=True %}
{% else %}
{% render_content comment markdown=False %}
{% endif %}
{% endblock comment_content %}
{% get_username_for_comment comment as username %}
<div class="{% block footer_wrapper_cls %}mt-2 text-muted{% endblock footer_wrapper_cls %}">
Expand Down
38 changes: 37 additions & 1 deletion comment/templatetags/comment_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django import template
from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe
from django.core.exceptions import ImproperlyConfigured

from comment.models import ReactionInstance, FlagInstance, Follower, BlockedUser
from comment.forms import CommentForm
Expand All @@ -14,6 +15,8 @@
from comment.managers import FlagInstanceManager
from comment.messages import ReactionError
from comment.context import DABContext
from comment.conf import settings


MULTIPLE_NEW_LINE_RE = re.compile(r'(.*)(\n){2,}(.*)')
SINGLE_NEW_LINE_RE = re.compile(r'(.*)(\n)(.*)')
Expand Down Expand Up @@ -97,7 +100,40 @@ def _restrict_line_breaks(content):
return SINGLE_NEW_LINE_RE.sub(r'\1<br>\3', content)


def render_content(comment, number=None):
def _render_markdown(content):
try:
import markdown as md
except ModuleNotFoundError:
raise ImproperlyConfigured(
'Comment App: Cannot render content in markdown format because markdown extension is not available.'
'You can install it by visting https://pypi.org/p/markdown or by using the command '
'"python -m pip install django-comments-dab[markdown]".'
)
else:
return md.markdown(
conditional_escape(content),
extensions=settings.COMMENT_MARKDOWN_EXTENSIONS,
extension_config=settings.COMMENT_MARKDOWN_EXTENSION_CONFIG
)


def render_content(comment, number=None, **kwargs):
markdown = kwargs.get('markdown', False)
if markdown:
if number:
warnings.warn(
(
'The argument number is ignored when markdown is set to "True".'
'No wrapping will take place for markdown formatted content.'
),
RuntimeWarning,
)
return {
'text_1': mark_safe(_render_markdown(comment.content)),
'text_2': '',
'urlhash': comment.urlhash,
}

try:
number = int(number)
except (ValueError, TypeError):
Expand Down
47 changes: 47 additions & 0 deletions comment/tests/test_template_tags.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import sys
from unittest.mock import patch

from django.core.exceptions import ImproperlyConfigured
Expand Down Expand Up @@ -164,6 +165,7 @@ def test_urlhash(self):
self.assertEqual(result['urlhash'], self.comment.urlhash)

@patch.object(settings, 'COMMENT_WRAP_CONTENT_WORDS', 20)
@patch.object(settings, 'COMMENT_ALLOW_MARKDOWN', False)
def test_content_wrapping_with_large_truncate_number(self):
content_words = self.comment.content.split()
self.assertIs(len(content_words) < 20, True)
Expand All @@ -173,6 +175,7 @@ def test_content_wrapping_with_large_truncate_number(self):
self.assertEqual(result['text_1'], self.comment.content)
self.assertIsNone(result['text_2'])

@patch.object(settings, 'COMMENT_ALLOW_MARKDOWN', False)
def test_single_line_breaks(self):
comment = self.parent_comment_1
comment.content = "Any long text\njust for testing render\ncontent function"
Expand All @@ -184,6 +187,7 @@ def test_single_line_breaks(self):
self.assertIn('<br>', result['text_1'])
self.assertNotIn('<br><br>', result['text_1'])

@patch.object(settings, 'COMMENT_ALLOW_MARKDOWN', False)
def test_multiple_line_breaks(self):
comment = self.parent_comment_1
comment.content = "Any long text\n\njust for testing render\n\n\ncontent function"
Expand All @@ -196,6 +200,7 @@ def test_multiple_line_breaks(self):
self.assertNotIn('<br><br><br>', result['text_1'])

@patch.object(settings, 'COMMENT_WRAP_CONTENT_WORDS', 5)
@patch.object(settings, 'COMMENT_ALLOW_MARKDOWN', False)
def test_content_wrapping_with_small_truncate_number(self):
self.comment.refresh_from_db()
content_words = self.comment.content.split()
Expand All @@ -207,6 +212,48 @@ def test_content_wrapping_with_small_truncate_number(self):
self.assertEqual(result['text_1'], ' '.join(content_words[:5]))
self.assertEqual(result['text_2'], ' '.join(content_words[5:]))

@patch.object(settings, 'COMMENT_ALLOW_MARKDOWN', True)
def test_raises_runtime_warning_passing_number_with_markdown_set_to_true(self):
msg = (
'The argument number is ignored when markdown is set to "True".'
'No wrapping will take place for markdown formatted content.'
)

with self.assertWarnsMessage(RuntimeWarning, msg):
result = render_content(self.comment, number=2, markdown=True)

# The content is surrounded by <p> tag to cater for connditional escaping which prevents from XSS attacks.
self.assertEqual(result['text_1'], f'<p>{self.comment.content}</p>')
self.assertEqual(result['text_2'], '')

@patch.object(settings, 'COMMENT_ALLOW_MARKDOWN', True)
def test_raises_improperly_configured_error_with_markdown_not_installed_and_markdown_set_to_true(self):
with patch.dict(sys.modules, {'markdown': None}):
from importlib import reload
reload(sys.modules['comment.templatetags.comment_tags'])
from comment.templatetags.comment_tags import render_content

msg = (
'Comment App: Cannot render content in markdown format because markdown extension is not available.'
'You can install it by visting https://pypi.org/p/markdown or by using the command '
'"python -m pip install django-comments-dab[markdown]".'
)

with self.assertRaisesMessage(ImproperlyConfigured, msg):
render_content(self.comment, markdown=True)

@patch.object(settings, 'COMMENT_ALLOW_MARKDOWN', True)
def test_rendering_markdown_content(self):
self.comment.content = '### Hi\n_italic_'
self.comment.save()
self.comment.refresh_from_db()

result = render_content(self.comment, markdown=True)

self.assertEqual(result['text_1'], '<h3>Hi</h3>\n<p><em>italic</em></p>')
self.assertEqual(result['text_2'], '')
self.assertEqual(result['urlhash'], self.comment.urlhash)


class GetUsernameForCommentTest(BaseTemplateTagsTest):
@classmethod
Expand Down
30 changes: 30 additions & 0 deletions docs/source/settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,33 @@ COMMENT_RESPONSE_FOR_BLOCKED_USER
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The response message for blocking reason. Default to ``You cannot perform this action at the moment! Contact the admin for more details``

COMMENT_ALLOW_MARKDOWN
^^^^^^^^^^^^^^^^^^^^^^

Enable rendering comment content in markdown format. Defaults to ``False``.

.. note::

When ``markdown`` format is being used to render content, no content wrapping is done. Passing a value for wrapping to the ``render_content`` template tag in such situations will raise a ``RuntimeWarning``.


.. _settings.comment_markdown_extensions:

COMMENT_MARKDOWN_EXTENSIONS
^^^^^^^^^^^^^^^^^^^^^^^^^^^

The list of extensions to be used for the rendering the ``markdown``. Defaults to ``['markdown.extensions.fenced_code']``. See `python markdown's documentation`_ for more information on this.

.. note::

Both ``COMMENT_MARKDOWN_EXTENSIONS`` and ``COMMENT_MARKDOWN_EXTENSION_CONFIG`` will only be used when ``COMMENT_ALLOW_MARKDOWN`` is set to ``True``.

.. _python markdown's documentation: https://python-markdown.github.io/extensions/extra/

.. _settings.comment_markdown_extension_config:

COMMENT_MARKDOWN_EXTENSION_CONFIGS
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The configuration used for markdown-extensions. Defaults to ``{}``. See `python markdown's documentation`_ for more information.
13 changes: 13 additions & 0 deletions docs/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,16 @@ Blocking functionality is added in version 2.7.0. It allows moderators to block

To enable blocking system set ``COMMENT_ALLOW_BLOCKING_USERS`` in ``settings`` to ``True``.
This will grant access for the **admins** only to block users. However, in order to give the **moderators** this right, you need to add ``COMMENT_ALLOW_MODERATOR_TO_BLOCK = True`` to `settings`


8. Enable Markdown format
^^^^^^^^^^^^^^^^^^^^^^^^^

This functionality was added in version ``2.8.0``. It allows comment content to be rendered using the power of ``markdown`` format.

To use this:
- Install additional dependency `python-markdown`_ may be installed using ``python -m pip install django-comments-dab[markdown]``.
- To enable set ``COMMENT_ALLOW_MARKDOWN`` to ``True`` in your ``settings`` file.
- For advanced configuration, you may use :ref:`settings.comment_markdown_extensions` and :ref:`settings.comment_markdown_extension_config`.

.. _python-markdown: https://pypi.org/p/markdown
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ include_package_data = True
install_requires = django
zip_safe = False

[options.extras_require]
markdown = markdown

[options.packages.find]
exclude =
docs
Expand Down
2 changes: 2 additions & 0 deletions test/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,5 @@

COMMENT_ALLOW_BLOCKING_USERS = True
COMMENT_ALLOW_MODERATOR_TO_BLOCK = True

COMMENT_ALLOW_MARKDOWN = True
3 changes: 3 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ deps =
django32: Django>=3.2,<4.0
djangomain: https://github.com/django/django/archive/main.tar.gz

extras =
markdown

usedevelop = True

commands =
Expand Down