Skip to content

Instantly share code, notes, and snippets.

@imsickofmaps
Forked from hakib/django_markdown.py
Created April 2, 2020 13:21
Show Gist options
  • Save imsickofmaps/b5c05d1ad2a67039386bdbeee1489401 to your computer and use it in GitHub Desktop.
Save imsickofmaps/b5c05d1ad2a67039386bdbeee1489401 to your computer and use it in GitHub Desktop.
Using Markdown in Django
# Source code for article:
# https://hakibenita.com/django-markdown
from typing import Optional
import re
import markdown
from markdown.inlinepatterns import LinkInlineProcessor, LINK_RE
from urllib.parse import urlparse
from django.core.exceptions import ValidationError
from django.core.validators import EmailValidator
from django.urls import reverse, resolve, Resolver404, NoReverseMatch
class Error(Exception):
pass
class InvalidMarkdown(Error):
def __init__(self, error: str, value: Optional[str] = None) -> None:
self.error = error
self.value = value
def __str__(self) -> str:
if self.value is None:
return self.error
return f'{self.error} "{self.value}"';
def get_site_domain() -> str:
# TODO: Get your site domain here
return 'example.com'
def clean_link(href: str, site_domain: str) -> str:
if href.startswith('mailto:'):
email_match = re.match('^(mailto:)?([^?]*)', href)
if not email_match:
raise InvalidMarkdown('Invalid mailto link', value=href)
email = email_match.group(2)
if email:
try:
EmailValidator()(email)
except ValidationError:
raise InvalidMarkdown('Invalid email address', value=email)
return href
# Remove fragments or query params before trying to match the url name
href_parts = re.search(r'#|\?', href)
if href_parts:
start_ix = href_parts.start()
url_name, url_extra = href[:start_ix], href[start_ix:]
else:
url_name, url_extra = href, ''
try:
url = reverse(url_name)
except NoReverseMatch:
pass
else:
return url + url_extra
parsed_url = urlparse(href)
if parsed_url.netloc == site_domain:
try:
resolver_match = resolve(parsed_url.path)
except Resolver404:
raise InvalidMarkdown(
"Should not use absolute links to the current site.\n"
"We couldn't find a match to this URL. Are you sure it exists?",
value=href,
)
else:
raise InvalidMarkdown(
'Should not use absolute links to the current site.\n'
'Try using the url name "{}".'.format(resolver_match.url_name),
value=href,
)
if parsed_url.scheme not in ('http', 'https'):
raise InvalidMarkdown('Must provide an absolute URL (be sure to include https:// or http://)', href)
return href
class DjangoLinkInlineProcessor(LinkInlineProcessor):
def getLink(self, data, index):
href, title, index, handled = super().getLink(data, index)
site_domain = get_site_domain()
href = clean_link(href, site_domain)
return href, title, index, handled
class DjangoUrlExtension(markdown.Extension):
def extendMarkdown(self, md, *args, **kwrags):
md.inlinePatterns.register(DjangoLinkInlineProcessor(LINK_RE, md), 'link', 160)
import markdown
from . import DjangoUrlExtension, get_site_domain
md = markdown.Markdown(extensions=[DjangoUrlExtension()])
html = md.convert("[Help](mailto:[email protected]?subject=I need help!)")
assert html == '<p><a href="mailto:[email protected]?subject=I need help!">Help</a></p>'
html = md.convert("[Help](mailto:?subject=I need help!)")
assert html == '<p><a href="mailto:?subject=I need help!">Help</a></p>'
try:
md.convert("[Help](mailto:invalidemail?subject=I need help!)")
except InvalidMarkdown as e:
assert e.error == 'Invalid email address'
assert e.value == 'invalidemail'
else:
assert False, 'Expected "InvalidMarkdown" error'
html = md.convert("Go back to [homepage](home)")
assert html == '<p>Go back to <a href="/">homepage</a></p>'
html = md.convert("Go back to [homepage](home#offers)")
assert html == '<p>Go back to <a href="/#offers">homepage</a></p>'
html = md.convert("Go back to [homepage](home?foo=bar)")
assert html == '<p>Go back to <a href="/?foo=bar">homepage</a></p>'
html = md.convert("Go back to [homepage](home?foo=bar#offers)")
assert html == '<p>Go back to <a href="/?foo=bar#offers">homepage</a></p>'
try:
md.convert(f'[link](https://{get_site_domain()}/foo)')
except InvalidMarkdown as e:
assert e.value == f'https://{get_site_domain()}/foo'
assert "We couldn't find a match to this URL." in e.error
else:
assert False, 'Expected "InvalidMarkdown" error'
try:
md.convert(f'[link](https://{get_site_domain()}/)')
except InvalidMarkdown as e:
assert e.value == f'https://{get_site_domain()}/'
assert 'Try using the url name' in e.error
else:
assert False, 'Expected "InvalidMarkdown" error'
html = md.convert('[link](https://external.com)')
assert html == '<p><a href="https://external.com">link</a></p>'
try:
md.convert('[link](external.com)')
except InvalidMarkdown as e:
assert e.value == 'external.com'
assert 'Must provide absolute url' in e.error
else:
assert False, 'Expected "InvalidMarkdown" error'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment