使用 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