Django:mark_safe的奇怪行为?

3 投票
1 回答
2974 浏览
提问于 2025-04-15 21:21

我写了一个小函数,用来输出HTML标签:

def html_tag(tag, content=None, close=True, attrs={}):
    lst = ['<',tag]
    for key, val in attrs.iteritems():
        lst.append(' %s="%s"' % (key, escape_html(val)))
    if close:
        if content is None: lst.append(' />')
        else: lst.extend(['>', content, '</', tag, '>'])
    else:
        lst.append('>')
    return mark_safe(''.join(lst))

这个函数运行得很好,但后来我看到一篇关于高效字符串连接的文章(我知道这对这个问题来说不太重要,但我想保持一致性),于是决定更新我的脚本:

def html_tag(tag, body=None, close=True, attrs={}):
    s = StringIO()
    s.write('<%s'%tag)
    for key, val in attrs.iteritems():
        s.write(' %s="%s"' % (key, escape_html(val)))
    if close:
        if body is None: s.write(' />')
        else: s.write('>%s</%s>' % (body, tag))
    else:
        s.write('>')
    return mark_safe(s.getvalue())

但是现在,当我试图从模板中渲染HTML时,它被转义了。其他部分完全一样。如果我把最后一行换成return mark_safe(unicode(s.getvalue())),它就能正常工作。我检查了s.getvalue()的返回类型。它应该是str,就像第一个函数一样,那为什么会出错呢?

SafeString(s.getvalue())也会出错,但用SafeUnicode(s.getvalue())就能成功。


我还想指出,我在另一个函数中用过return mark_safe(s.getvalue()),并没有出现奇怪的行为。


这个“调用栈”看起来是这样的:

class Input(Widget):
    def render(self):
        return html_tag('input', attrs={'type':self.itype, 'id':self.id,
            'name':self.name, 'value':self.value, 'class':self.itype})
class Field:
    def __unicode__(self):
        return mark_safe(self.widget.render())

然后{{myfield}}在模板中。所以它确实被mark_safed了两次,我本以为这可能是问题所在,但我也试着去掉了这个……我真的不知道是什么导致的这个问题,不过解决起来也不太难,所以我想我就不太担心了。

1 个回答

7

你的小部件的 render 方法是通过 BoundField.__unicode__ 函数来调用的,这个函数返回的是 SafeString,而不是 unicode 的一个子类。

在 Django 的很多地方(比如 django.template.VariableNode.render),实际上会对字段实例本身调用 force_unicode。这样做的结果就是执行 unicode(instance.__unicode__()),所以即使 instance.__unicode__() 返回的是一个 SafeString 对象,它最终会变成一个普通的 unicode 对象。

为了说明这一点,看看下面的代码片段:

from django.utils.encoding import force_unicode
from django.utils.safestring import mark_safe

class Foo(object):
    def __unicode__(self):
        return mark_safe("foo")

foo = Foo()
print "foo =", type(foo)

ufoo = unicode(foo)
print "ufoo =", type(ufoo)

forced_foo = force_unicode(foo)
print "forced_foo =", type(forced_foo)


bar = mark_safe("bar")
print "bar =", type(bar)

forced_bar = force_unicode(bar)
print "forced_bar =", type(forced_bar)

输出:

foo = <class 'testt.Foo'>
ufoo = <type 'unicode'>
forced_foo = <type 'unicode'>
bar = <class 'django.utils.safestring.SafeString'>
forced_bar = <class 'django.utils.safestring.SafeUnicode'>

撰写回答