使用 Node 的高级自定义标签

有时候你想做的事情对于一个人来说太复杂了。要知道这一点,你需要创建一个编译功能和一个渲染器。

在此示例中,我们将使用以下语法创建模板标记 verbose_name

描述
{% verbose_name obj %} 模型的详细名称
{% verbose_name obj 'status' %} 字段状态的详细名称
{% verbose_name obj plural %} 详细名称复数的模型
{% verbose_name obj plural capfirst %} 大写的详细名称复数模型
{% verbose_name obj 'foo' capfirst %} 字段的大写详细名称
{% verbose_name obj field_name %} 变量中字段的详细名称
{% verbose_name obj 'foo'|add:'_bar' %} 字段“foo_bar”的详细名称

我们不能用简单的标签做到这一点的原因是 pluralcapfirst 既不是变量也不是字符串,它们是关键字。我们显然可以决定将它们作为字符串'plural''capfirst'传递,但它可能与具有这些名称的字段冲突。{% verbose_name obj 'plural' %} 是指“obj 的详细名称复数”还是“obj.plural 的详细名称”?

首先让我们创建编译功能:

@register.tag(name='verbose_name')
def do_verbose_name(parser, token):
    """
    - parser: the Parser object. We will use it to parse tokens into
              nodes such as variables, strings, ...
    - token: the Token object. We will use it to iterate each token
             of the template tag.
    """
    # Split tokens within spaces (except spaces inside quotes)
    tokens = token.split_contents()
    tag_name = tokens[0]
    try:
        # each token is a string so we need to parse it to get the actual
        # variable instead of the variable name as a string.
        model = parser.compile_filter(tokens[1])
    except IndexError:
        raise TemplateSyntaxError(
            "'{}' tag requires at least 1 argument.".format(tag_name))

    field_name = None
    flags = {
        'plural': False,
        'capfirst': False,
    }

    bits = tokens[2:]
    for bit in bits:
        if bit in flags.keys():
            # here we don't need `parser.compile_filter` because we expect
            # 'plural' and 'capfirst' flags to be actual strings.
            if flags[bit]:
                raise TemplateSyntaxError(
                    "'{}' tag only accept one occurrence of '{}' flag".format(
                        tag_name, bit)
                )
            flags[bit] = True
            continue
        if field_name:
            raise TemplateSyntaxError((
                "'{}' tag only accept one field name at most. {} is the second "
                "field name encountered."
            ).format(tag_name, bit)
        field_name = parser.compile_filter(bit)

    # VerboseNameNode is our renderer which code is given right below
    return VerboseNameNode(model, field_name, **flags)

现在是渲染器:

class VerboseNameNode(Node):

    def __init__(self, model, field_name=None, **flags):
        self.model = model
        self.field_name = field_name
        self.plural = flags.get('plural', False)
        self.capfirst = flags.get('capfirst', False)

    def get_field_verbose_name(self):
        if self.plural:
            raise ValueError("Plural is not supported for fields verbose name.")
        return self.model._meta.get_field(self.field_name).verbose_name

    def get_model_verbose_name(self):
       if self.plural:
           return self.model._meta.verbose_name_plural
       else:
           return self.model._meta.verbose_name

    def render(self, context):
        """This is the main function, it will be called to render the tag.
        As you can see it takes context, but we don't need it here.
        For instance, an advanced version of this template tag could look for an
        `object` or `object_list` in the context if `self.model` is not provided.
        """
        if self.field_name:
            verbose_name = self.get_field_verbose_name()
        else:
            verbose_name = self.get_model_verbose_name()
        if self.capfirst:
            verbose_name = verbose_name.capitalize()
        return verbose_name