-
-
Save imsickofmaps/b5c05d1ad2a67039386bdbeee1489401 to your computer and use it in GitHub Desktop.
Using Markdown in Django
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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