Django - mark only a specific part of a string / user input as safe?
User input looks like this:
Test 'post'. Post at 8:52 on Feb 3rd. /u/username created it.
This <a href="link">link</a> should not be displayed as a link.
I send user input through a custom filter showing it on the template. This is a custom filter:
word_split_re = re.compile(r'(\s+)')
@register.filter
@stringfilter
def customUrlize(value):
words = word_split_re.split(force_text(value))
for i, b in enumerate(words):
if b.startswith('/u/'):
username = b[3:]
if re.match("^[A-Za-z0-9_-]*$", username):
b = "<a href='testLink'>" + b + "</a>"
words[i] = mark_safe(b)
return ''.join(words)
As you can see what I want to do is wrap words starting with '/ u /' (And only letters, numbers, underscores and dashes) with
<a>
tag. With the current filter, all the code is escaped and it appears as:
Test 'post'. Post at 8:52 on Feb 3rd. <a href='testLink'>/u/username</a> created it.
This <a href="link">link</a> should not be displayed as a link.
I want the text to display fine, but for / u / username was a link.
If I try to do:
return mark_safe(''.join(words))
then it displays even
<a href="link">link</a>
as a reference along with
/u/username
How to make it display only
/u/username
how is the link?
Edit: I am using Django 1.5.
In my template, assuming
comment
is an
CharField
I am displaying the comment like this:
{{ comment|customUrlize }}
source to share
If there is no additional formatting in the text that you want to keep, you can just plain escape
text before changing it.
Returns the specified text, with ampersands, quotes, and angle brackets, encoded for use in HTML. The input is first passed through
force_text()
, and the output is passed throughmark_safe()
.
So this line:
words = word_split_re.split(force_text(value))
Becomes as follows:
words = word_split_re.split(escape(value))
Full filter:
from django.utils.html import escape
word_split_re = re.compile(r'(\s+)')
@register.filter
@stringfilter
def customUrlize(value):
words = word_split_re.split(escape(value))
for i, b in enumerate(words):
if b.startswith('/u/'):
username = b[3:]
if re.match("^[A-Za-z0-9_-]*$", username):
b = "<a href='testLink'>" + b + "</a>"
words[i] = mark_safe(b)
return mark_safe(''.join(words))
And must give:
Test 'post'. Post at 8:52 on Feb 3rd. <a href='testLink'>/u/username</a> created it.
This <a href="link">link</a> should not be displayed as a link.
which displays as:
Check "message". Posted at 8:52 am on February 3rd. / u / username created it. This <a href = "link"> link </a> should not appear as a link.
It might not help depending on your needs, but you can just split the string in half and in the template label only the first one with | safe.
For example:
a = "Test 'post'. Post at 8:52 on Feb 3rd. <a href='testLink'>/u/username</a> created it. This <a href='link'>link</a> should not be displayed as a link."
b = a.split('it.')
Then just pass it to the template as
'string1': b[0]
'string2': b[1]
or whatever and then {{string1|safe}} <br> {{string2}}
in the template.
The output will be the way you wanted. Without this, of course. But this is easy to fix.
source to share