Django Tag personalizzati avanzati usando il nodo


Esempio

A volte ciò che vuoi fare è troppo complesso per un filter o un semplice simple_tag . Fow questo è necessario creare una funzione di compilazione e un renderer.

In questo esempio creeremo un tag template verbose_name con la seguente sintassi:

Esempio Descrizione
{% verbose_name obj %} Nome dettagliato di un modello
{% verbose_name obj 'status' %} Nome dettagliato del campo "stato"
{% verbose_name obj plural %} Nome prolisso plurale di un modello
{% verbose_name obj plural capfirst %} Nome dettagliato in maiuscolo plurale di un modello
{% verbose_name obj 'foo' capfirst %} Nome dettagliato in maiuscolo di un campo
{% verbose_name obj field_name %} Nome dettagliato di un campo da una variabile
{% verbose_name obj 'foo'|add:'_bar' %} Nome dettagliato di un campo "foo_bar"

Il motivo per cui non possiamo farlo con un semplice tag è che plural e capfirst non sono né variabili né stringhe, sono "parole chiave". Ovviamente potremmo decidere di passarli come stringhe 'plural' e 'capfirst' , ma potrebbe entrare in conflitto con i campi con questi nomi. {% verbose_name obj 'plural' %} significa "nome dettagliato plurale di obj " o "nome dettagliato di obj.plural "?

Per prima cosa creiamo la funzione di compilazione:

@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)

E ora il renderer:

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