.. .. META INFORMATION OF TRANSLATION .. .. $TranslationStatus: Done, waiting for revision $ .. $OriginalRevision: 11332 $ .. $TranslationAuthors: Robson Mendonça $ .. .. INFO OF THIS FILE (DO NOT EDIT! UPDATED BY SUBVERSION) .. .. $HeadURL$ .. $LastChangedRevision$ .. $LastChangedBy$ .. $LastChangedDate$ .. ========================================= Tags e filtros de template personalizados ========================================= Introdução ========== O sistema de template do Django vem com uma larga variedade de :doc:`tags e filtros embutidos ` projetados para direcionar a lógida da apresentação que sua aplicação. Todavia, você pode se encontrar precisando de funcionalidades que não são cobertas pelo conjunto de primitivas de template. Você pode extender o motor de template definindo tags e filtros personalizados usando Python, e então torná-los disponíveis aos seus templates usando a tag ``{% load %}``. Layout do código ---------------- Tags e filtros de template customizados devem estar dentro de uma Django app. Se elas estão relacionadas a uma app existente, faz mais sentido estarem empacotados na app; caso contrário, você deveria criar uma nova app para armazená-los. A app deve conter um diretório ``templatetags``, no mesmo nível do ``models.py``, ``views.py``, etc. Se ele não existe ainda, é só criá-lo - não esqueça o arquivo ``__init__.py`` para assegurar que o diretório seja tratado como um pacote Python. Suas tags e filtros personalizados ficaram nesse módulo dentro do diretório ``templatetags``. O nome do arquivo do módulo é o nome que você usará para carregar as tags depois, então seja cuidadoso ao criar um nome que não colida com as tags e filtros de outras apps. Por exemplo, se sua tags/filtros estão num arquivo chamado ``poll_extras.py``, o layout de sua app deve parecer com isso:: polls/ models.py templatetags/ __init__.py poll_extras.py views.py E nos seus templates você poderia usar o seguinte: .. code-block:: html+django {% load poll_extras %} A app que contém as tags personalizadas deve estar no :setting:`INSTALLED_APPS` para que a tag ``{% load %}`` funcione. Esta é uma medida de segunrança: Ela permite que você armazene código Python para muitas bibliotecas de template num único servidor, sem permitir o acesso a todas elas para toda instalação do Django. Não há limites de quantos módulos você pode colocar no pácote ``templatetags``. Só tenha em mente que uma declaração ``{% load %}`` carregará tags/filtros para uma dado nome de módulo Python, não o nome da app. Para uma biblioteca de tag ser válida, o módulo deve conter uma variável chamada ``register`` que é uma instância do ``templates.Library``, na qual todas as tags e filtros são registrados. Então, no topo de seu módulo, coloque o seguinte:: from django import template register = template.Library() .. admonition:: Por trás das cenas Para uma tonelada de exemplos, leia o código font dos filtros e tags padrão do Django. Eles estão em ``django/templates/defaultfilters.py`` e ``django/template/defaulttags.py``, respectivamente. Escrevendo filtros de template personalizados --------------------------------------------- Filtros personalizados são como funções Python que recebem um ou dois argumentos: * O valor de uma variável (input) -- não necessariamente uma string. * O valor de um argumento -- este pode ter um valor padrão, ou ser deixado de fora. Por exemplo, no filtro ``{{ var|foo:"bar"}}``, ao filtro ``foo`` seria passado a variável ``var`` e o argumento ``"bar"``. Funções filtro devem sem retornar algo. Elas não lançam exceções. Elas falham silenciosamente. No caso de um erro, elas devem retornar a entrada original ou uma string vazia -- o que fizer mais sentido. Aqui temos um exemplo de definição de filtro:: def cut(value, arg): "Remove todos os valores do arg da string fornecida." return value.replace(arg, '') E aqui tem um exemplo de como este filtro poderia ser usado: .. code-block:: html+django {{ somevariable|cut:"0" }} A maioria dos filtros não recebem argumentos. Neste caso, é só deixar o argumento fora de sua função. Exemplo:: def lower(value): # Somente um argumento. "Converte uma string para minúsculo" return value.lower() Filtros de template que esperam strings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Se você estiver escrevendo um filtro de template que somente expera uma string como o primeiro argumento, você deve usar o decorador ``stringfilter``. Este converterá um objeto para seu valor em string antes de ser passado para a sua função:: from django.template.defaultfilters import stringfilter @stringfilter def lower(value): return value.lower() Desta forma, você será poderá passar, digo, um inteiro para este filtro, e ele não causará um ``AttributeError`` (pois inteiros não possuem métodos ``lower()``). Registrando filtros personalizados ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Uma vez que tenha escrito sua definição de filtro, você precisa registrá-lo na sua instância ``Library``, para torná-lo disponível na linguagem de template do Django:: register.filter('cut', cut) register.filter('lower', lower) O método ``Library.filter()`` recebe dois argumentos: 1. O nome do filtro -- uma string. 2. A função de compilação -- uma função Python (não o nome da função como uma string). Você pode usar ``register.filter()`` como um decorador, no entanto:: @register.filter(name='cut') @stringfilter def cut(value, arg): return value.replace(arg, '') @register.filter @stringfilter def lower(value): return value.lower() Se você deixa desligado o argumento ``name``, como no segundo exemplo acima, o Django usará o nome da função como o nome do filtro. Filtros e auto-escaping ~~~~~~~~~~~~~~~~~~~~~~~ Quando esiver escrevendo um filtro personalizado, pense um pouco em como o filtro irá interagir com o comportamento de auto-escaping do Django. Note que há três tipos de strings que podem ser passadas por dentro de um código de template: * **Strings puras** são tipos nativos do Python ``str`` ou ``unicode``. Na saída, elas são escapadas se o auto-escaping estiver em efeito e apresenta-se inalterado. * **Strings seguras** são strings que foram marcadas como seguras por um escape fora de tempo. Qualquer escape necessário já foi feito. Elas são comumente usados para a saída que contém HTML puro que destina-se a ser interpretado, assim como está, no lado do cliente. Internamente, estas strings são do typo ``SafeString`` ou ``SafeUnicode``. Elas compartilham uma classe básica comum de ``SafeData``, então você pode testá-las usando um código como este:: if isinstance(value, SafeData): # Faça algo com a string "safe". * **Strings marcadas como "precisando escapar"** são *sempre* escapadas na saída, indiferente se elas estão num bloco ``autoescape`` ou não. Essas strings são somente escapadas uma vez, contudo, mesmo se o auto-escaping se aplica. Internamente, estas strings são do tipo ``EscapeString`` ou ``EscapeUnicode``. Geralmente você não tem de se preocupar com eles; eles existem para implementação do filtro ``escape``. Código de filtros de template caem em uma de duas situações: 1. Seu filtro não introduz quaisquer caracteres HTML não seguros (``<``, ``>``, ``'``, ``"`` or ``&``) no resultado que ainda não foi aprensentado. Neste caso, você pode deixar o Django se preocupar com todo o auto-escaping por você. Tudo que você precisa fazer é colocar o atributo ``is_safe`` sobre o sua função filtro e setá-lo como ``True``, assim:: @register.filter def myfilter(value): return value myfilter.is_safe = True Este atributo diz ao Django que se uma string "safe" é passada para o seu filtro, o resultado será mantido "safe" e se uma string não-safe é passada, o Django automaticamente a escapará, se necessário. Você pode pensar que nisso como significando "este filtro é seguro -- ele não introduz qualquer possibilidade de HTML não seguro." A razão de ``is_safe`` ser necessário é porque há abundância de operadores de string normais que tornarão um objeto ``SafeData`` uma string normal ``str`` ou ``unicode`` e, ao invés de tentar pegá-los todos, o que será muito difícil, o Django repara o dano depois que o filtro foi completado. Por exemplo, suponhamos que você tem um filtro que adiciona uma string ``xx`` no final de uma entrada. Já que isso não introduz caracteres HTML perigosos ao resultado (com exceção de alguns que já estavam presentes), você deve marcar seu filtro com ``is_safe``:: @register.filter def add_xx(value): return '%sxx' % value add_xx.is_safe = True Quando este filtro é usado num template onde auto-escaping está habilitado, o Django escapa a saída sempre que a entrada já não estiver marcada como "safe". Por padrão, ``is_safe`` é ``False``, e você pode omiti-lo de qualquer filtro onde ele não é requerido. Seja cuidadoso quando decidir se seu filtro realmente deixa strings safe como safe. Se estiver *removendo* caracteres, você pode inadvertidamente deixa tabs HTML desbalanceadas ou entidades no resultado. Por exemplo, removendo um ``>`` de uma entrada por tornar um ```` em ``A hora é {% current_time "%Y-%m-%d %I:%M %p" %}.
.. _`sintaxe do strftime`: http://docs.python.org/library/time.html#time.strftime O parser para esta função deve abarrar o parâmetro e criar um objeto ``Node``:: from django import template def do_current_time(parser, token): try: # split_contents() sabe que não é para dividir strings entre aspas. tag_name, format_string = token.split_contents() except ValueError: raise template.TemplateSyntaxError, "%r tag requires a single argument" % token.contents.split()[0] if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")): raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name return CurrentTimeNode(format_string[1:-1]) Notas: * O ``parser`` é o objeto parser de template. Não não precisamos dele nesse exemplo. * O ``token.contents`` é uma string de conteúdos puros da tag. No nosso exemplo, é ``'current_time "%Y-%m-%d %I:%M %p"'``. * O método ``token.split_contents()`` quebra os argumentos nos espaços mantendo as strings entre aspas agrupadas. O mais simples ``token.contents.split()`` não seria tão robusto, e dividiria ingenuamente *todos* os espaços, incluindo aqueles dentro de strings entre aspas agrupadas. É uma boa idéia sempre usar ``token.split_contents()``. * Essa função é responsável por lançar ``django.template.TemplateSyntaxError``, com mensagens úteis, para qualquer erro de sintaxe. * As exceções ``TemplateSystemError`` usam a variável ``tag_name``. Não embuta o nome da tag nas suas mensagens de erro, porque eles casam com o nome de sua função. O ``token.contents.split()[0]`` ''sempre'' será o nome da sua tag -- mesmo quando a tag não tenha argumentos. * A função retorna um ``CurrentTimeNode`` com tudo o que o nodo precisa saber sobre esta tag. Neste caso, é só passar o argumento -- ``"%Y-%m-%d %I:%M %p"``. As aspas, no início e no final, da template tag são removidas no ``format_string['1:-1']``. * O parseamento é de muito baixo nível. Os desenvolvedores do Django têm experimentado escrever pequenos frameworks sobre o estes sistema de parse, usando técnicas como gramáticas EBNF, mas estes experimentos tornam o motor de template muito lento. Ele é de baixo nível porque é mais rápido. Escrevendo o renderizador ~~~~~~~~~~~~~~~~~~~~~~~~~ O segundo passo em escrever tags personalizadas é definir uma subclasse ``Node`` que tem um método ``render()``. Continuando o exemplo acima, nós precisamos definir ``CurrentTimeNode``:: from django import template import datetime class CurrentTimeNode(template.Node): def __init__(self, format_string): self.format_string = format_string def render(self, context): return datetime.datetime.now().strftime(self.format_string) Notas: * ``__init__()`` recebe o ``format_string`` do ``do_current_time()``. Sempre passe quaisquer opções/parâmetros/argumentos para um ``Node`` via seu ``__init__()``. * O método ``render()`` é onde o trabalho realmente acontece. * O ``render()`` nunca deve lançar um ``TemplateSyntaxError`` ou qualquer outra exceção. Ele deve falhar silenciosamente, assim como os filtros de template o fazem. Ultimamente, essa dissociação de compilação e renderização resulta num sistema de template eficiente, pois um template pode renderizar vários contextos sem precisar ser parsiado múltiplas vezes. Considerações do Auto-escaping ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A saída de uma template tag *não* é automaticamente executada através de filtros de auto-escaping. Entretanto, ainda há algumas coisas que você deve ter em mente quando estiver escrevendo uma template tag. Se a função ``render()`` de seu template armazena o resultado numa variável de contexto (ao invés de retornar o resultado numa string), ela deverá se preocupar em chamar ``mark_safe()`` se for apropriado. Quando a variável é finalmente renderizada, ela será afetada pela configuração auto-escape em efeito nessa hora, assim o conteúdo que deveria ser seguro além de escapado precisa ser marcado como tal. Também, se sua template tag cria um novo contexto para executar algumas sub-renderizações, configure o atributo auto-escape para o valor do contexto atual. O método ``__init__`` para a classe ``Context`` recebe um parâmetro chamado ``autoescape`` que você pode usar para este propósito. Por exemplo:: def render(self, context): # ... new_context = Context({'var': obj}, autoescape=context.autoescape) # ... Faça algo com new_context ... Isso não é uma situação muito comum, mas é útil se você estiver renderizando um template você mesmo. Por exemplo:: def render(self, context): t = template.loader.get_template('small_fragment.html') return t.render(Context({'var': obj}, autoescape=context.autoescape)) Se nós tivéssemos esquecido de passar o valor atual ``context.autoescape`` para nosso novo ``Context`` neste exemplo, os resultados teriam *sempre* de ser automaticamente escapados, o que pode não ser um comportamento desejável se a template tag é usada dentro de um bloco ``{% autoescape off %}``. .. _template_tag_thread_safety: Thread-safety considerations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 1.2 Once a node is parsed, its ``render`` method may be called any number of times. Since Django is sometimes run in multi-threaded environments, a single node may be simultaneously rendering with different contexts in response to two separate requests. Therefore, it's important to make sure your template tags are thread safe. To make sure your template tags are thread safe, you should never store state information on the node itself. For example, Django provides a builtin ``cycle`` template tag that cycles among a list of given strings each time it's rendered:: {% for o in some_list %}The time is {{ current_time }}.
Porém, há um problema com ``CurrentTimeNode2``: O nome da variável ``current_time`` é nativo. Isso significa que você precisará assegurar-se de que seu template não use ``{{ current_time }}`` em nenhum lugar mais, por que o ``{% current_time %}`` será cegamente sobrescrito pelo valor desta variável. Uma solução clara é fazer uma template tag especificar o nome da variável de saída, desta forma:: .. code-block:: html+django {% get_current_time "%Y-%M-%d %I:%M %p" as my_current_time %}The current time is {{ my_current_time }}.
Para fazer isso, você precisar refatorar ambos funções de compilação e classe ``Node``, tipo:: class CurrentTimeNode3(template.Node): def __init__(self, format_string, var_name): self.format_string = format_string self.var_name = var_name def render(self, context): context[self.var_name] = datetime.datetime.now().strftime(self.format_string) return '' import re def do_current_time(parser, token): # Essa versão usa uma expressão regular para parsear a o conteúdo da tag. try: # Separando None == separando por espaços. tag_name, arg = token.contents.split(None, 1) except ValueError: raise template.TemplateSyntaxError, "%r tag requires arguments" % token.contents.split()[0] m = re.search(r'(.*?) as (\w+)', arg) if not m: raise template.TemplateSyntaxError, "%r tag had invalid arguments" % tag_name format_string, var_name = m.groups() if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")): raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name return CurrentTimeNode3(format_string[1:-1], var_name) A diferença aqui é que o ``do_current_time()`` pega a string de formato e o nome da variável, passando ambos ao ``CurrenttimeNode3``. Parseando até outro tag de bloco ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Template tags podem funcionar em conjunto. Por exemplo, a tag padrão ``{% comment %}`` esconde tudo até o ``{% endcomment %}``. Para criar uma template tag como esta, use ``parser.parse()`` na sua função de compilação. Aqui temos como a tag padrão ``{% comment %}`` é implementado:: def do_comment(parser, token): nodelist = parser.parse(('endcomment',)) parser.delete_first_token() return CommentNode() class CommentNode(template.Node): def render(self, context): return '' O ``parser.parse()`` recebe uma tupla de nomes de tags de bloco ''para parsear até ela''. Ela retorna uma instância do ``django.template.NodeList``, que é uma lista de todos objetos ``Node`` que o parser encontrar ''antes'' de encontrar quaisquer tags chamadas na tupla. No ``"nodelist = parser.parse(('endcomment',))"`` do exemplo acima, ``nodelist`` é uma lista de todos os nodos entre o ``{% comment %}`` e ``{% endcomment %}``, não contando ``{% comment %}`` e ``{% endcomment %}``. Depois que ``parser.parse()`` é chamado, o parser ainda não "consumiu" a tag ``{% endcomment %}``, assim o código precisa explicitamente chamar ``parser.delete_first_token()``. O ``CommentNode.render()`` simplesmente retorna uma string vazia. Qualquer coisa entre ``{% comment %}`` e ``{% endcomment %}`` é ignorado. Parseando até outra tag de bloco, e salvando conteúdos ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ No exemplo anterior, ``do_comment()`` tudo descartado entre ``{% comment %}`` e ``{% endcomment %}``. Ao invés de fazer isso, é possível fazer algo com o código entre as tags de bloco. Por exemplo, há uma template tag personalizada, ``{% upper %}``, que capitaliza tudo entre ela mesma e ``{% endupper %}``. Uso: .. code-block:: html+django {% upper %}Isso irá aparecer em maiúsuclas, {{ your_name }}.{% endupper %} Como no exemplo anterior, nós usaremos ``parser.parse()``. Mas dessa vez, nós passamos o resultado ``nodelist`` para o ``Node``:: def do_upper(parser, token): nodelist = parser.parse(('endupper',)) parser.delete_first_token() return UpperNode(nodelist) class UpperNode(template.Node): def __init__(self, nodelist): self.nodelist = nodelist def render(self, context): output = self.nodelist.render(context) return output.upper() O único conceito novo aqui é o ``self.nodelist.render(context)`` no ``UpperNode.render()``. Para mais exemplos de complexidade de renderização, vej o código fonte ``{% if %}``, ``{% for %}``, ``{% ifequal %}`` e ``{% ifchanged %}``. Eles estão em ``django/template/defaulttags.py``.