diff --git a/h/accounts/schemas.py b/h/accounts/schemas.py
index 5d75d3fb11e..81d74aa6d44 100644
--- a/h/accounts/schemas.py
+++ b/h/accounts/schemas.py
@@ -4,7 +4,7 @@
import colander
import deform
-from jinja2 import Markup
+from markupsafe import Markup
from h import i18n, models
from h.models.user import (
diff --git a/h/form.py b/h/form.py
index 6b47aa7fdb1..016f3d448ac 100644
--- a/h/form.py
+++ b/h/form.py
@@ -5,8 +5,8 @@
form templates in preference to the defaults.
"""
import deform
-import jinja2
import pyramid_jinja2
+from markupsafe import Markup
from pyramid import httpexceptions
from pyramid.path import AssetResolver
@@ -45,7 +45,7 @@ def __call__(self, template_name, **kwargs):
context = self._system.copy()
context.update(kwargs)
- return jinja2.Markup(template.render(context))
+ return Markup(template.render(context))
def create_environment(base):
diff --git a/h/presenters/annotation_html.py b/h/presenters/annotation_html.py
index 9bd38c286e8..0309fa9f5a5 100644
--- a/h/presenters/annotation_html.py
+++ b/h/presenters/annotation_html.py
@@ -1,5 +1,5 @@
-import jinja2
from dateutil import parser
+from markupsafe import Markup, escape
from h.presenters.document_html import DocumentHTMLPresenter
@@ -16,7 +16,7 @@ def __init__(self, annotation):
@property
def uri(self):
- return jinja2.escape(self.annotation.target_uri)
+ return escape(self.annotation.target_uri)
@property
def text_rendered(self):
@@ -28,15 +28,15 @@ def text_rendered(self):
care of all necessary escaping.
"""
if self.annotation.text_rendered:
- return jinja2.Markup(self.annotation.text_rendered)
- return jinja2.Markup("")
+ return Markup(self.annotation.text_rendered)
+ return Markup("")
@property
def quote(self):
"""Get the text in the document which this annotation refers to."""
selection = self._get_selection()
if selection:
- return jinja2.escape(selection)
+ return escape(selection)
return ""
@@ -55,12 +55,12 @@ def description(self):
selection = self._get_selection()
if selection:
- selection = jinja2.escape(selection)
+ selection = escape(selection)
description += f"<blockquote>{selection}</blockquote>"
text = self.annotation.text
if text:
- text = jinja2.escape(text)
+ text = escape(text)
description += f"{text}"
return description
@@ -74,7 +74,7 @@ def created_day_string(self):
date.
"""
- created_string = jinja2.escape(self.annotation.created)
+ created_string = escape(self.annotation.created)
return parser.parse(created_string).strftime("%Y-%m-%d")
@property
diff --git a/h/presenters/document_html.py b/h/presenters/document_html.py
index f6864d27805..fd8f1ec0074 100644
--- a/h/presenters/document_html.py
+++ b/h/presenters/document_html.py
@@ -1,6 +1,6 @@
from urllib.parse import unquote, urlparse
-import jinja2
+import markupsafe
class DocumentHTMLPresenter:
@@ -24,7 +24,7 @@ def filename(self):
"""
if self.uri.lower().startswith("file:///"):
- return jinja2.escape(self.uri.split("/")[-1])
+ return markupsafe.escape(self.uri.split("/")[-1])
return ""
@property
@@ -44,7 +44,7 @@ def href(self):
"""
if self.document.web_uri:
- return jinja2.escape(self.document.web_uri)
+ return markupsafe.escape(self.document.web_uri)
return ""
@property
@@ -64,14 +64,14 @@ def hostname_or_filename(self):
object so that it doesn't get double-escaped.
"""
if self.filename:
- return jinja2.escape(unquote(self.filename))
+ return markupsafe.escape(unquote(self.filename))
hostname = urlparse(self.uri).hostname
# urlparse()'s .hostname is sometimes None.
hostname = hostname or ""
- return jinja2.escape(hostname)
+ return markupsafe.escape(hostname)
@property
def link(self):
@@ -129,7 +129,7 @@ def link_text(self):
Markup object so it doesn't get double-escaped.
"""
- title = jinja2.escape(self.title)
+ title = markupsafe.escape(self.title)
# Sometimes self.title is the annotated document's URI (if the document
# has no title). In those cases we want to remove the http(s):// from
@@ -160,17 +160,17 @@ def title(self):
# Convert non-string titles into strings.
# We're assuming that title cannot be a byte string.
title = str(title)
- return jinja2.escape(title)
+ return markupsafe.escape(title)
if self.filename:
- return jinja2.escape(unquote(self.filename))
+ return markupsafe.escape(unquote(self.filename))
- return jinja2.escape(unquote(self.uri))
+ return markupsafe.escape(unquote(self.uri))
@property
def uri(self):
if self.document.document_uris:
- return jinja2.escape(self.document.document_uris[0].uri)
+ return markupsafe.escape(self.document.document_uris[0].uri)
return ""
@property
@@ -205,7 +205,7 @@ def truncate(content, length=55):
if len(content) <= length:
return content
- return content[:length] + jinja2.Markup("…")
+ return content[:length] + markupsafe.Markup("…")
host_or_filename = truncate(host_or_filename)
link_text = truncate(link_text)
@@ -220,10 +220,10 @@ def truncate(content, length=55):
link += "
{host_or_filename}"
link = link.format(
- href=jinja2.escape(href),
- title=jinja2.escape(title),
- link_text=jinja2.escape(link_text),
- host_or_filename=jinja2.escape(host_or_filename),
+ href=markupsafe.escape(href),
+ title=markupsafe.escape(title),
+ link_text=markupsafe.escape(link_text),
+ host_or_filename=markupsafe.escape(host_or_filename),
)
- return jinja2.Markup(link)
+ return markupsafe.Markup(link)
diff --git a/h/views/accounts.py b/h/views/accounts.py
index 5d3abfbf1e2..b368c3c5463 100644
--- a/h/views/accounts.py
+++ b/h/views/accounts.py
@@ -4,7 +4,7 @@
import colander
import deform
-import jinja2
+from markupsafe import Markup
from pyramid import httpexceptions, security
from pyramid.exceptions import BadCSRFToken
from pyramid.view import view_config, view_defaults
@@ -285,7 +285,7 @@ def _reset_password(self, user, password):
svc.update_password(user, password)
self.request.session.flash(
- jinja2.Markup(
+ Markup(
_(
"Your password has been reset. You can now log in with "
"your new password."
@@ -321,7 +321,7 @@ def get_when_not_logged_in(self):
activation = models.Activation.get_by_code(self.request.db, code)
if activation is None:
self.request.session.flash(
- jinja2.Markup(
+ Markup(
_(
"We didn't recognize that activation link. "
"Have you already activated your account? "
@@ -340,7 +340,7 @@ def get_when_not_logged_in(self):
user.activate()
self.request.session.flash(
- jinja2.Markup(
+ Markup(
_(
"Your account has been activated! "
"You can now log in using the password you provided."
@@ -369,14 +369,12 @@ def get_when_logged_in(self):
# The user is already logged in to the account (so the account
# must already be activated).
self.request.session.flash(
- jinja2.Markup(
- _("Your account has been activated and you're logged in.")
- ),
+ Markup(_("Your account has been activated and you're logged in.")),
"success",
)
else:
self.request.session.flash(
- jinja2.Markup(
+ Markup(
_(
"You're already logged in to a different account. "
'Log out and open the activation link '
diff --git a/h/views/activity.py b/h/views/activity.py
index 0041bd5d5e8..6ad5e426f6d 100644
--- a/h/views/activity.py
+++ b/h/views/activity.py
@@ -2,7 +2,7 @@
from urllib.parse import urlparse
-from jinja2 import Markup
+from markupsafe import Markup
from pyramid import httpexceptions
from pyramid.view import view_config, view_defaults
diff --git a/h/views/admin/groups.py b/h/views/admin/groups.py
index e1a0c18b32e..f8719d94339 100644
--- a/h/views/admin/groups.py
+++ b/h/views/admin/groups.py
@@ -1,4 +1,4 @@
-from jinja2 import Markup
+from markupsafe import Markup
from pyramid.httpexceptions import HTTPFound
from pyramid.view import view_config, view_defaults
diff --git a/h/views/admin/organizations.py b/h/views/admin/organizations.py
index 8217e1098fa..07c292e6496 100644
--- a/h/views/admin/organizations.py
+++ b/h/views/admin/organizations.py
@@ -1,4 +1,4 @@
-from jinja2 import Markup
+from markupsafe import Markup
from pyramid.httpexceptions import HTTPFound
from pyramid.view import view_config, view_defaults
from sqlalchemy import func
diff --git a/h/views/admin/users.py b/h/views/admin/users.py
index 91fe83d956e..08ddd8eb161 100644
--- a/h/views/admin/users.py
+++ b/h/views/admin/users.py
@@ -1,4 +1,4 @@
-import jinja2
+from markupsafe import Markup
from pyramid import httpexceptions
from pyramid.view import view_config
@@ -70,7 +70,7 @@ def users_activate(request):
request.session.flash(
# pylint:disable=consider-using-f-string
- jinja2.Markup(_("User {name} has been activated!".format(name=user.username))),
+ Markup(_("User {name} has been activated!".format(name=user.username))),
"success",
)
@@ -145,7 +145,7 @@ def users_delete(request):
@view_config(context=UserNotFoundError)
def user_not_found(exc, request): # pragma: no cover
- request.session.flash(jinja2.Markup(_(exc.message)), "error")
+ request.session.flash(Markup(_(exc.message)), "error")
return httpexceptions.HTTPFound(location=request.route_path("admin.users"))
diff --git a/tests/unit/h/presenters/annotation_html_test.py b/tests/unit/h/presenters/annotation_html_test.py
index d3abbe45ae3..e31e7a2ad52 100644
--- a/tests/unit/h/presenters/annotation_html_test.py
+++ b/tests/unit/h/presenters/annotation_html_test.py
@@ -1,7 +1,7 @@
import datetime
import pytest
-from jinja2 import Markup
+from markupsafe import Markup
from h.presenters.annotation_html import AnnotationHTMLPresenter
diff --git a/tests/unit/h/presenters/document_html_test.py b/tests/unit/h/presenters/document_html_test.py
index 38fbafc97ee..d107546155d 100644
--- a/tests/unit/h/presenters/document_html_test.py
+++ b/tests/unit/h/presenters/document_html_test.py
@@ -1,7 +1,7 @@
from unittest import mock
-import jinja2
import pytest
+from markupsafe import Markup, escape
from h.presenters.document_html import DocumentHTMLPresenter
@@ -26,7 +26,7 @@ def test_filename_returns_Markup(self):
document_uris=[mock.Mock(uri="file:///home/seanh/MyFile.pdf")]
)
- assert isinstance(presenter.filename, jinja2.Markup)
+ assert isinstance(presenter.filename, Markup)
def test_filename_with_FILE_uri(self):
presenter = self.presenter(
@@ -65,7 +65,7 @@ def test_href_returns_empty_string_for_document_with_no_web_uri(self):
def test_href_returns_Markup(self):
web_uri = "http://www.example.com/example.html"
- assert isinstance(self.presenter(web_uri=web_uri).href, jinja2.Markup)
+ assert isinstance(self.presenter(web_uri=web_uri).href, Markup)
link_text_fixtures = pytest.mark.usefixtures("title")
@@ -105,12 +105,12 @@ def test_link_text_with_https_title(self, title):
@link_text_fixtures
def test_link_text_returns_Markup_if_title_returns_Markup(self, title):
for title_ in (
- jinja2.Markup("Example Document"),
- jinja2.Markup("http://www.example.com/example.html"),
- jinja2.Markup("https://www.example.com/example.html"),
+ Markup("Example Document"),
+ Markup("http://www.example.com/example.html"),
+ Markup("https://www.example.com/example.html"),
):
title.return_value = title_
- assert isinstance(self.presenter().link_text, jinja2.Markup)
+ assert isinstance(self.presenter().link_text, Markup)
hostname_or_filename_fixtures = pytest.mark.usefixtures("uri", "filename")
@@ -122,9 +122,9 @@ def test_hostname_or_filename_returns_filename_for_files(self, filename):
@hostname_or_filename_fixtures
def test_hostname_or_filename_returns_Markup_if_filename_does(self, filename):
- filename.return_value = jinja2.Markup("MyFile.pdf")
+ filename.return_value = Markup("MyFile.pdf")
- assert isinstance(self.presenter().hostname_or_filename, jinja2.Markup)
+ assert isinstance(self.presenter().hostname_or_filename, Markup)
@hostname_or_filename_fixtures
def test_hostname_or_filename_unquotes_filenames(self, filename):
@@ -142,9 +142,9 @@ def test_hostname_or_filename_returns_hostname_for_non_files(self, uri, filename
@hostname_or_filename_fixtures
def test_hostname_or_filename_returns_Markup_when_uri_does(self, uri, filename):
filename.return_value = ""
- uri.return_value = jinja2.Markup("http://www.example.com/example.html")
+ uri.return_value = Markup("http://www.example.com/example.html")
- assert isinstance(self.presenter().hostname_or_filename, jinja2.Markup)
+ assert isinstance(self.presenter().hostname_or_filename, Markup)
@hostname_or_filename_fixtures
def test_hostname_or_filename_with_empty_string_for_uri(self, uri, filename):
@@ -178,10 +178,10 @@ def test_title_escapes_html_in_document_titles(self):
title = self.presenter(title=spam_link).title
- assert jinja2.escape(spam_link) in title
+ assert escape(spam_link) in title
for char in ["<", ">", '"', "'"]:
assert char not in title
- assert isinstance(title, jinja2.Markup)
+ assert isinstance(title, Markup)
@title_fixtures
def test_title_with_file_uri(self, filename):
@@ -193,9 +193,9 @@ def test_title_with_file_uri(self, filename):
@title_fixtures
def test_title_returns_Markup_when_filename_returns_Markup(self, filename):
- filename.return_value = jinja2.Markup("MyFile.pdf")
+ filename.return_value = Markup("MyFile.pdf")
- assert isinstance(self.presenter(title=None).title, jinja2.Markup)
+ assert isinstance(self.presenter(title=None).title, Markup)
@title_fixtures
def test_title_unquotes_uris(self, uri, filename):
@@ -207,9 +207,9 @@ def test_title_unquotes_uris(self, uri, filename):
@title_fixtures
def test_title_returns_Markup_when_uri_returns_Markup(self, uri, filename):
filename.return_value = "" # This is not a file:// URI.
- uri.return_value = jinja2.Markup("http://example.com/example.html")
+ uri.return_value = Markup("http://example.com/example.html")
- assert isinstance(self.presenter(title=None).title, jinja2.Markup)
+ assert isinstance(self.presenter(title=None).title, Markup)
@title_fixtures
def test_title_when_document_has_None_for_title(self, uri, filename):