Django
Tag e filtri modello
Ricerca…
Filtri personalizzati
Filtri consente di applicare una funzione a una variabile. Questa funzione può richiedere 0 o 1 argomento. Ecco la sintassi:
{{ variable|filter_name }}
{{ variable|filter_name:argument }}
I filtri possono essere concatenati, quindi questo è perfettamente valido:
{{ variable|filter_name:argument|another_filter }}
Se tradotto in python la riga precedente darebbe qualcosa di simile a questo:
print(another_filter(filter_name(variable, argument)))
In questo esempio, scriveremo un filtro personalizzato verbose_name
che si applica a un modello (istanza o classe) oa un oggetto QuerySet. Restituirà il nome dettagliato di un modello o il suo nome prolisso plurale se l'argomento è impostato su True
.
@register.filter
def verbose_name(model, plural=False):
"""Return the verbose name of a model.
`model` can be either:
- a Model class
- a Model instance
- a QuerySet
- any object refering to a model through a `model` attribute.
Usage:
- Get the verbose name of an object
{{ object|verbose_name }}
- Get the plural verbose name of an object from a QuerySet
{{ objects_list|verbose_name:True }}
"""
if not hasattr(model, '_meta'):
# handle the case of a QuerySet (among others)
model = model.model
opts = model._meta
if plural:
return opts.verbose_name_plural
else:
return opts.verbose_name
Tag semplici
Il modo più semplice per definire un tag modello personalizzato è utilizzare un simple_tag
. Questi sono molto semplici da configurare. Il nome della funzione sarà il nome del tag (sebbene sia possibile sovrascriverlo) e gli argomenti saranno token ("parole" separati da spazi, ad eccezione degli spazi racchiusi tra virgolette). Supporta anche argomenti di parole chiave.
Ecco un tag inutile che illustrerà il nostro esempio:
{% useless 3 foo 'hello world' foo=True bar=baz.hello|capfirst %}
Lascia che foo
e baz
siano variabili di contesto come la seguente:
{'foo': "HELLO", 'baz': {'hello': "world"}}
Diciamo che vogliamo questo tag molto inutile per renderizzare in questo modo:
HELLO;hello world;bar:World;foo:True<br/>
HELLO;hello world;bar:World;foo:True<br/>
HELLO;hello world;bar:World;foo:True<br/>
Tipo di concatenazione degli argomenti ripetuta 3 volte (3 è il primo argomento).
Ecco come può essere l'implementazione del tag:
from django.utils.html import format_html_join
@register.simple_tag
def useless(repeat, *args, **kwargs):
output = ';'.join(args + ['{}:{}'.format(*item) for item in kwargs.items()])
outputs = [output] * repeat
return format_html_join('\n', '{}<br/>', ((e,) for e in outputs))
format_html_join
consente di contrassegnare <br/>
come HTML sicuro, ma non il contenuto delle outputs
.
Tag personalizzati avanzati usando il nodo
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