# Django 6.0.7 documentation

# Så här skapar du anpassade malltaggar och filter

Djangos mallspråk levereras med ett brett utbud av inbyggda taggar och filter som är utformade för att tillgodose presentationslogikbehoven i din applikation. Ändå kan det hända att du behöver funktionalitet som inte täcks av kärnuppsättningen av mallprimitiver. Du kan utöka mallmotorn genom att definiera anpassade taggar och filter med Python och sedan göra dem tillgängliga för dina mallar med hjälp av taggen `{% load %}`.

## Layout för kod

Den vanligaste platsen för att ange anpassade malltaggar och filter är inuti en Django-app. Om de relaterar till en befintlig app är det vettigt att samla dem där; annars kan de läggas till i en ny app. När en Django-app läggs till i `INSTALLED_APPS`, görs alla taggar som den definierar på den konventionella plats som beskrivs nedan automatiskt tillgängliga för laddning i mallar.

Appen bör innehålla en katalog med namnet `templatetags`, på samma nivå som `models.py`, `views.py` osv. Om den inte redan finns, skapa den - glöm inte filen `__init__.py` för att se till att katalogen behandlas som ett Python-paket.

Utvecklingsservern startar inte om automatiskt

När du har lagt till modulen `templatetags` måste du starta om servern innan du kan använda taggarna eller filtren i mallarna.

Dina anpassade taggar och filter kommer att finnas i en modul i katalogen `templatetags`. Namnet på modulfilen är det namn som du använder för att ladda taggarna senare, så var noga med att välja ett namn som inte krockar med anpassade taggar och filter i en annan app.

Om dina anpassade taggar/filter till exempel finns i en fil som heter `poll_extras.py`, kan din applayout se ut så här:

    polls/
        __init__.py
        models.py
        templatetags/
            __init__.py
            poll_extras.py
        views.py

Och i din mall skulle du använda följande:

    {% load poll_extras %}

Appen som innehåller de anpassade taggarna måste finnas i `INSTALLED_APPS` för att taggen `{% load %}` ska fungera. Detta är en säkerhetsfunktion: Det gör att du kan vara värd för Python-kod för många mallbibliotek på en enda värdmaskin utan att aktivera åtkomst till dem alla för varje Django-installation.

Det finns ingen gräns för hur många moduler du lägger in i paketet `templatetags`. Tänk bara på att en `{% load %}`-sats kommer att ladda taggar/filter för det angivna Python-modulnamnet, inte namnet på appen.

För att vara ett giltigt taggbibliotek måste modulen innehålla en variabel på modulnivå som heter `register` som är en `template.Library`-instans, där alla taggar och filter registreras. Så, nära toppen av din modul, lägg till följande:

    from django import template

    register = template.Library()

Alternativt kan malltaggmoduler registreras via argumentet `'libraries'` till `DjangoTemplates`. Detta är användbart om du vill använda en annan etikett än namnet på template-taggmodulen när du laddar template-taggar. Det gör det också möjligt för dig att registrera taggar utan att installera en applikation.

Bakom kulisserna

För massor av exempel, läs källkoden för Djangos standardfilter och taggar. De finns i django/template/defaultfilters.py respektive django/template/defaulttags.py.

För mer information om taggen `load`, läs dess dokumentation.

## Skriva anpassade mallfilter

Anpassade filter är Python-funktioner som tar ett eller två argument:

- Värdet på variabeln (input) - inte nödvändigtvis en sträng.

- Argumentets värde - detta kan ha ett standardvärde eller utelämnas helt och hållet.

I filtret `{{ var|foo:"bar" }}` får till exempel filtret `foo` variabeln `var` och argumentet `"bar"`.

Eftersom mallspråket inte tillhandahåller undantagshantering kommer alla undantag som skapas av ett mallfilter att exponeras som ett serverfel. Därför bör filterfunktioner undvika att skapa undantag om det finns ett rimligt fallback-värde att returnera. När det gäller indata som representerar en tydlig bugg i en mall kan det fortfarande vara bättre att skapa ett undantag än ett tyst misslyckande som döljer buggen.

Här är ett exempel på en filterdefinition:

    def cut(value, arg):
        """Removes all values of arg from the given string"""
        return value.replace(arg, "")

Och här är ett exempel på hur det filtret skulle kunna användas:

    {{ somevariable|cut:"0" }}

De flesta filter tar inte emot argument. I detta fall, lämna argumentet utanför din funktion:

    def lower(value):  # Only one argument.
        """Converts a string into all lowercase"""
        return value.lower()

### Registrering av anpassade filter

django.template.Library.filter()

När du har skrivit din filterdefinition måste du registrera den med din `Library`-instans för att göra den tillgänglig för Djangos mallspråk:

    register.filter("cut", cut)
    register.filter("lower", lower)

Metoden `Library.filter()` tar två argument:

1. Filtrets namn - en sträng.

2. Kompileringsfunktionen – en Python-funktion (inte namnet på funktionen som en sträng).

Du kan använda `register.filter()` som en dekorator istället:

    @register.filter(name="cut")
    def cut(value, arg):
        return value.replace(arg, "")

    @register.filter
    def lower(value):
        return value.lower()

Om du utelämnar argumentet `name`, som i det andra exemplet ovan, kommer Django att använda funktionens namn som filternamn.

Slutligen accepterar `register.filter()` också tre nyckelordsargument, `is_safe`, `needs_autoescape` och `expects_localtime`. Dessa argument beskrivs i filter och auto-escaping och filter och tidszoner nedan.

### Mallfilter som förväntar sig strängar

django.template.defaultfilters.stringfilter()

Om du skriver ett mallfilter som bara förväntar sig en sträng som första argument, bör du använda dekoratorn `stringfilter`. Detta kommer att konvertera ett objekt till dess strängvärde innan det skickas till din funktion:

    from django import template
    from django.template.defaultfilters import stringfilter

    register = template.Library()

    @register.filter
    @stringfilter
    def lower(value):
        return value.lower()

På så sätt kan du skicka t.ex. ett heltal till detta filter utan att det orsakar ett `AttributeError` (eftersom heltal inte har `lower()`-metoder).

### Filter och automatisk särskrivning

När du skriver ett anpassat filter, tänk på hur filtret kommer att interagera med Djangos auto-escaping-beteende. Observera att två typer av strängar kan skickas runt inuti mallkoden:

- Raw strings är de ursprungliga Python-strängarna. Vid utmatning escapas de om automatisk escaping är i bruk och presenteras annars oförändrade.

- Säkra strängar är strängar som har markerats som säkra från ytterligare escaping vid utmatningstillfället. All nödvändig escaping har redan gjorts. De används ofta för utdata som innehåller rå HTML som är avsedd att tolkas som den är på klientsidan.

  Internt är dessa strängar av typen `SafeString`. Du kan testa för dem med hjälp av kod som:

      from django.utils.safestring import SafeString

      if isinstance(value, SafeString):
          # Do something with the "safe" string.
          ...

Mallfilterkod faller i en av två situationer:

1. Ditt filter introducerar inte några HTML-osäkra tecken (`<`, `>`, `'`, `"` eller `&`) i resultatet som inte redan fanns. I det här fallet kan du låta Django ta hand om all hantering av automatisk eskapning åt dig. Allt du behöver göra är att ställa in flaggan `is_safe` till `True` när du registrerar din filterfunktion, så här:

       @register.filter(is_safe=True)
       def myfilter(value):
           return value

   Denna flagga talar om för Django att om en ”säker” sträng skickas in i ditt filter, kommer resultatet fortfarande att vara ”säkert” och om en icke-säker sträng skickas in, kommer Django automatiskt att undkomma den, om det behövs.

   Du kan tänka dig att detta betyder ”det här filtret är säkert - det introducerar inte någon möjlighet till osäker HTML”

   Anledningen till att `is_safe` är nödvändig är att det finns gott om normala strängoperationer som kommer att göra ett `SafeData`-objekt tillbaka till ett normalt `str`-objekt och i stället för att försöka fånga dem alla, vilket skulle vara mycket svårt, reparerar Django skadan efter att filtret har slutförts.

   Anta till exempel att du har ett filter som lägger till strängen `xx` i slutet av alla inmatningar. Eftersom detta inte tillför några farliga HTML-tecken till resultatet (förutom de som redan fanns där), bör du markera ditt filter med `is_safe`:

       @register.filter(is_safe=True)
       def add_xx(value):
           return "%sxx" % value

   När detta filter används i en mall där automatisk escaping är aktiverad, kommer Django att escapa utdata när indata inte redan är markerad som ”säker”.

   Som standard är `is_safe` `False`, och du kan utelämna det från alla filter där det inte krävs.

   Var försiktig när du avgör om ditt filter verkligen lämnar säkra strängar som säkra. Om du tar bort tecken kan du oavsiktligt lämna obalanserade HTML-taggar eller enheter i resultatet. Om du till exempel tar bort en `>` från indata kan `<a>` bli `<a`, som måste escapas i utdata för att inte orsaka problem. På samma sätt kan borttagning av ett semikolon (`;`) förvandla `&amp;` till `&amp;amp`, som inte längre är en giltig enhet och därför behöver ytterligare escaping. De flesta fall kommer inte att vara så här knepiga, men håll utkik efter sådana problem när du granskar din kod.

   Om du markerar ett filter med `is_safe` kommer filtrets returvärde att omvandlas till en sträng. Om ditt filter ska returnera ett booleskt värde eller ett annat icke-strängvärde, kommer markeringen `is_safe` troligen att få oönskade konsekvenser (till exempel att det booleska värdet False omvandlas till strängen ’False’).

2. Alternativt kan din filterkod manuellt ta hand om all nödvändig escaping. Detta är nödvändigt när du introducerar ny HTML-markup i resultatet. Du vill markera utdata som säker från ytterligare escaping så att din HTML-markering inte escapas ytterligare, så du måste hantera inmatningen själv.

   För att markera utdata som en säker sträng, använd `django.utils.safestring.mark_safe()`.

   Var dock försiktig. Du behöver göra mer än att bara markera utdata som säker. Du måste se till att det verkligen är säkert, och vad du gör beror på om auto-escaping är i kraft. Tanken är att skriva filter som kan fungera i mallar där auto-escaping är antingen på eller av för att göra saker enklare för dina mallförfattare.

   För att ditt filter ska känna till det aktuella auto-escaping-läget ska du ställa in flaggan `needs_autoescape` till `True` när du registrerar din filterfunktion. (Om du inte anger den här flaggan är standardvärdet `False`). Denna flagga talar om för Django att din filterfunktion vill få ett extra nyckelordsargument, kallat `autoescape`, som är `True` om auto-escaping är i kraft och `False` annars. Det rekommenderas att ställa in standardvärdet för parametern `autoescape` till `True`, så att om du anropar funktionen från Python-kod kommer den att ha escaping aktiverad som standard.

   Låt oss t.ex. skriva ett filter som betonar det första tecknet i en sträng:

       from django import template
       from django.utils.html import conditional_escape
       from django.utils.safestring import mark_safe

       register = template.Library()

       @register.filter(needs_autoescape=True)
       def initial_letter_filter(text, autoescape=True):
           first, other = text[0], text[1:]
           if autoescape:
               esc = conditional_escape
           else:
               esc = lambda x: x
           result = "<strong>%s</strong>%s" % (esc(first), esc(other))
           return mark_safe(result)

   Flaggan `needs_autoescape` och nyckelordsargumentet `autoescape` innebär att vår funktion kommer att veta om automatisk escaping är i kraft när filtret anropas. Vi använder `autoescape` för att avgöra om indata behöver skickas genom `django.utils.html.conditional_escape` eller inte. (I det senare fallet använder vi identitetsfunktionen som ”escape”-funktion.) Funktionen `conditional_escape()` är som `escape()` förutom att den bara escaper indata som inte är en `SafeData`-instans. Om en `SafeData`-instans skickas till `conditional_escape()` returneras data oförändrat.

   Slutligen, i exemplet ovan, kommer vi ihåg att markera resultatet som säkert så att vår HTML infogas direkt i mallen utan ytterligare escaping.

   Det finns ingen anledning att oroa sig för flaggan `is_safe` i det här fallet (även om det inte skulle skada något att inkludera den). När du manuellt hanterar auto-escaping-problemen och returnerar en säker sträng, kommer `is_safe`-flaggan inte att ändra någonting på något sätt.

Varning

Undvik XSS-sårbarheter när du återanvänder inbyggda filter

Djangos inbyggda filter har `autoescape=True` som standard för att få rätt autoescaping-beteende och undvika en cross-site script-sårbarhet.

I äldre versioner av Django bör du vara försiktig när du återanvänder Djangos inbyggda filter eftersom `autoescape` som standard är `None`. Du måste skicka `autoescape=True` för att få autoescaping.

Om du till exempel vill skriva ett anpassat filter som heter `urlize_and_linebreaks` och som kombinerar filtren `urlize`` och `linebreaksbr``, skulle filtret se ut på följande sätt:

    from django.template.defaultfilters import linebreaksbr, urlize

    @register.filter(needs_autoescape=True)
    def urlize_and_linebreaks(text, autoescape=True):
        return linebreaksbr(urlize(text, autoescape=autoescape), autoescape=autoescape)

Då så:

    {{ comment|urlize_and_linebreaks }}

skulle motsvara:

    {{ comment|urlize|linebreaksbr }}

### Filter och tidszoner

Om du skriver ett anpassat filter som fungerar på `datetime`-objekt, registrerar du det vanligtvis med flaggan `expects_localtime` inställd på `True`:

    @register.filter(expects_localtime=True)
    def businesshours(value):
        try:
            return 9 <= value.hour < 17
        except AttributeError:
            return ""

När denna flagga är inställd, om det första argumentet till ditt filter är en tidszonmedveten datatid, kommer Django att konvertera den till den aktuella tidszonen innan den skickas till ditt filter när det är lämpligt, enligt regler för tidszonkonverteringar i mallar.

## Skriva anpassade malltaggar

Taggar är mer komplexa än filter, eftersom taggar kan göra vad som helst. Django tillhandahåller ett antal genvägar som gör det enklare att skriva de flesta typer av taggar. Först ska vi utforska dessa genvägar och sedan förklara hur man skriver en tagg från grunden för de fall då genvägarna inte är tillräckligt kraftfulla.

### Enkla taggar

django.template.Library.simple_tag()

Många malltaggar tar emot ett antal argument - strängar eller mallvariabler - och returnerar ett resultat efter att ha gjort en viss bearbetning baserad enbart på inmatningsargumenten och viss extern information. Till exempel kan en `current_time`-tagg acceptera en formatsträng och returnera tiden som en sträng formaterad i enlighet med detta.

För att underlätta skapandet av dessa typer av taggar tillhandahåller Django en hjälpfunktion, `simple_tag`. Denna funktion, som är en metod för `django.template.Library`, tar en funktion som accepterar valfritt antal argument, sveper in den i en `render`-funktion och de andra nödvändiga bitarna som nämns ovan och registrerar den med mallsystemet.

Vår funktion `current_time` kan alltså skrivas så här:

    import datetime
    from django import template

    register = template.Library()

    @register.simple_tag
    def current_time(format_string):
        return datetime.datetime.now().strftime(format_string)

Några saker att notera om hjälpfunktionen `simple_tag`:

- Kontrollen av det nödvändiga antalet argument etc. har redan gjorts när vår funktion anropas, så vi behöver inte göra det.

- Citaten runt argumentet (om sådana finns) har redan tagits bort, så vi får en vanlig sträng.

- Om argumentet var en mallvariabel skickas variabelns aktuella värde till vår funktion, inte variabeln i sig.

Till skillnad från andra taggverktyg skickar `simple_tag` sin utdata genom `conditional_escape()` om mallkontexten är i autoescape-läge, för att säkerställa korrekt HTML och skydda dig från XSS-sårbarheter.

Om ytterligare escaping inte är önskvärt måste du använda `mark_safe()` om du är helt säker på att din kod inte innehåller XSS-sårbarheter. För att bygga små HTML-snuttar rekommenderas starkt att använda `format_html()` istället för `mark_safe()`.

Om din malltagg behöver komma åt den aktuella kontexten kan du använda argumentet `takes_context` när du registrerar din tagg:

    @register.simple_tag(takes_context=True)
    def current_time(context, format_string):
        timezone = context["timezone"]
        return your_get_current_time_method(timezone, format_string)

Observera att det första argumentet måste heta `context`.

För mer information om hur alternativet `takes_context` fungerar, se avsnittet om inclusion tags.

Om du behöver byta namn på din tagg kan du ange ett eget namn för den:

    register.simple_tag(lambda x: x - 1, name="minusone")

    @register.simple_tag(name="minustwo")
    def some_function(value):
        return value - 2

`simple_tag`-funktioner kan acceptera valfritt antal positionella eller nyckelordsargument. Till exempel:

    @register.simple_tag
    def my_tag(a, b, *args, **kwargs):
        warning = kwargs["warning"]
        profile = kwargs["profile"]
        ...
        return ...

I mallen kan sedan valfritt antal argument, åtskilda med mellanslag, skickas till malltaggen. Precis som i Python anges värdena för nyckelordsargument med hjälp av likhetstecknet (”`=`”) och måste anges efter de positionella argumenten. Till exempel:

    {% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}

Det är möjligt att lagra taggresultaten i en mallvariabel i stället för att skriva ut dem direkt. Detta görs genom att använda argumentet `as` följt av variabelnamnet. På så sätt kan du själv mata ut innehållet där du tycker att det passar:

    {% current_time "%Y-%m-%d %I:%M %p" as the_time %}
    <p>The time is {{ the_time }}.</p>

### Enkla block-taggar

django.template.Library.simple_block_tag()

When a section of rendered template needs to be passed into a custom tag, Django provides the `simple_block_tag` helper function to accomplish this. Similar to `simple_tag()`, this function accepts a custom tag function, but with the additional `content` argument, which contains the rendered content as defined inside the tag. This allows dynamic template sections to be easily incorporated into custom tags.

Till exempel kan en anpassad blocktagg som skapar ett diagram se ut så här:

    from django import template
    from myapp.charts import render_chart

    register = template.Library()

    @register.simple_block_tag
    def chart(content):
        return render_chart(source=content)

Argumentet `content` innehåller allt mellan taggarna `{% chart %}` och `{% endchart %}`:

    {% chart %}
      digraph G {
          label = "Chart for {{ request.user }}"
          A -> {B C}
      }
    {% endchart %}

Om det finns andra malltaggar eller variabler i `content`-blocket kommer de att renderas innan de skickas till taggfunktionen. I exemplet ovan kommer `request.user` att vara löst när `render_chart` anropas.

Blocktaggar avslutas med `end{name}` (t.ex. `endchart`). Detta kan anpassas med parametern `end_name`:

    @register.simple_block_tag(end_name="endofchart")
    def chart(content):
        return render_chart(source=content)

Vilket skulle kräva en malldefinition som denna:

    {% chart %}
      digraph G {
          label = "Chart for {{ request.user }}"
          A -> {B C}
      }
    {% endofchart %}

Några saker att notera om `simple_block_tag`:

- Det första argumentet måste heta `content`, och det kommer att innehålla innehållet i malltaggen som en renderad sträng.

- Variabler som skickas till taggen inkluderas inte i innehållets renderingskontext, vilket skulle vara fallet om man använde taggen `{% with %}`.

Precis som simple_tag, `simple_block_tag`:

- Validerar kvantiteten och kvaliteten på argumenten.

- Tar bort citat från argumenten om det behövs.

- Undviker utgången i enlighet med detta.

- Stöder passering av `takes_context=True` vid registreringstillfället för att komma åt kontexten. Observera att i det här fallet måste det första argumentet till den anpassade funktionen mås kallas `context` och `content` måste följa efter.

- Stöder att byta namn på taggen genom att skicka argumentet `name` vid registrering.

- Accepterar valfritt antal positionella argument eller nyckelordsargument.

- Stöder lagring av resultatet i en mallvariabel med hjälp av `as`-varianten.

Innehåll som undkommer

`simple_block_tag` beter sig på liknande sätt som `simple_tag` när det gäller automatisk escaping. För detaljer om escaping och säkerhet, se `simple_tag`. Eftersom argumentet `content` redan har återgivits av Django, är det redan escapat.

#### Ett komplett exempel

Tänk dig en anpassad malltagg som genererar en meddelanderuta som stöder flera meddelandenivåer och innehåll utöver en enkel fras. Detta kan implementeras med hjälp av en `simple_block_tag` enligt följande:

`testapp/templatetags/testapptags.py`

    from django import template
    from django.utils.html import format_html

    register = template.Library()

    @register.simple_block_tag(takes_context=True)
    def msgbox(context, content, level):
        format_kwargs = {
            "level": level.lower(),
            "level_title": level.capitalize(),
            "content": content,
            "open": " open" if level.lower() == "error" else "",
            "site": context.get("site", "My Site"),
        }
        result = """
        <div class="msgbox {level}">
          <details{open}>
            <summary>
              <strong>{level_title}</strong>: Please read for <i>{site}</i>
            </summary>
            <p>
              {content}
            </p>
          </details>
        </div>
        """
        return format_html(result, **format_kwargs)

I kombination med en minimal vy och motsvarande mall, som visas här:

`testapp/views.py`

    from django.shortcuts import render

    def simpleblocktag_view(request):
        return render(request, "test.html", context={"site": "Important Site"})

`testapp/templates/test.html` `testapp/templates/test.html`

    {% extends "base.html" %}

    {% load testapptags %}

    {% block content %}

      {% msgbox level="error" %}
        Please fix all errors. Further documentation can be found at
        <a href="http://example.com">Docs</a>.
      {% endmsgbox %}

      {% msgbox level="info" %}
        More information at: <a href="http://othersite.com">Other Site</a>/
      {% endmsgbox %}

    {% endblock %}

Följande HTML visas som renderad utdata:

    <div class="msgbox error">
      <details open>
        <summary>
          <strong>Error</strong>: Please read for <i>Important Site</i>
        </summary>
        <p>
          Please fix all errors. Further documentation can be found at
          <a href="http://example.com">Docs</a>.
        </p>
      </details>
    </div>

    <div class="msgbox info">
      <details>
        <summary>
          <strong>Info</strong>: Please read for <i>Important Site</i>
        </summary>
        <p>
          More information at: <a href="http://othersite.com">Other Site</a>
        </p>
      </details>
    </div>

### Taggar för inkludering

django.template.Library.inclusion_tag()

En annan vanlig typ av malltagg är den typ som visar vissa data genom att rendera en annan mall. Till exempel använder Djangos admin-gränssnitt anpassade malltaggar för att visa knapparna längst ner på formulärsidorna för ”lägg till/ändra”. Dessa knappar ser alltid likadana ut, men länkmålen ändras beroende på vilket objekt som redigeras - så de är ett perfekt fall för att använda en liten mall som är fylld med detaljer från det aktuella objektet. (I administratörens fall är detta taggen `submit_row`)

Den här typen av taggar kallas ”inclusion tags”.

Att skriva inclusion-taggar demonstreras förmodligen bäst med ett exempel. Låt oss skriva en tagg som matar ut en lista med val för ett givet `Poll`-objekt, som skapades i tutorials. Vi kommer att använda taggen så här:

    {% show_results poll %}

…och utdata blir ungefär så här:

    <ul>
      <li>First choice</li>
      <li>Second choice</li>
      <li>Third choice</li>
    </ul>

Först definierar vi den funktion som tar argumentet och producerar en ordbok med data som resultat. Den viktiga punkten här är att vi bara behöver returnera en ordbok, inte något mer komplext. Detta kommer att användas som en mallkontext för mallfragmentet. Exempel:

    def show_results(poll):
        choices = poll.choice_set.all()
        return {"choices": choices}

Därefter skapar du den mall som används för att rendera taggens utdata. Denna mall är en fast egenskap hos taggen: taggskrivaren specificerar den, inte malldesignern. I vårt exempel är mallen mycket kort:

    <ul>
    {% for choice in choices %}
        <li> {{ choice }} </li>
    {% endfor %}
    </ul>

Nu skapar och registrerar du inclusion-taggen genom att anropa metoden `inclusion_tag()` på ett `Library`-objekt. Om vi följer vårt exempel och ovanstående mall finns i en fil som heter `results.html` i en katalog som malladdaren söker i, skulle vi registrera taggen så här:

    # Here, register is a django.template.Library instance, as before
    @register.inclusion_tag("results.html")
    def show_results(poll): ...

Alternativt är det möjligt att registrera inkluderingstaggen med hjälp av en `django.template.Template`-instans:

    from django.template.loader import get_template

    t = get_template("results.html")
    register.inclusion_tag(t)(show_results)

…när du först skapade funktionen.

Ibland kan dina inkluderingstaggar kräva ett stort antal argument, vilket gör det till en smärta för mallförfattare att skicka in alla argument och komma ihåg deras ordning. För att lösa detta tillhandahåller Django ett `takes_context`-alternativ för inkluderingstaggar. Om du anger `takes_context` när du skapar en malltagg, kommer taggen inte att ha några nödvändiga argument, och den underliggande Python-funktionen kommer att ha ett argument - mallkontexten från och med när taggen anropades.

Låt oss till exempel säga att du skriver en inclusion-tagg som alltid kommer att användas i ett sammanhang som innehåller variablerna `home_link` och `home_title` som pekar tillbaka till huvudsidan. Så här skulle Python-funktionen se ut:

    @register.inclusion_tag("link.html", takes_context=True)
    def jump_link(context):
        return {
            "link": context["home_link"],
            "title": context["home_title"],
        }

Observera att den första parametern till funktionen måste heta `context`.

I raden `register.inclusion_tag()` angav vi `takes_context=True` och namnet på mallen. Så här kan mallen `link.html` se ut:

    Jump directly to <a href="{{ link }}">{{ title }}</a>.

När du sedan vill använda den anpassade taggen laddar du dess bibliotek och anropar den utan några argument, till exempel så här:

    {% jump_link %}

Observera att när du använder `takes_context=True` behöver du inte skicka några argument till malltaggen. Den får automatiskt tillgång till kontexten.

Parametern `takes_context` är som standard inställd på `False`. När den är inställd på `True` skickas taggen till kontextobjektet, som i det här exemplet. Det är den enda skillnaden mellan det här fallet och det tidigare exemplet med `inclusion_tag`.

`inclusion_tag`-funktioner kan acceptera valfritt antal positionella argument eller nyckelord. Till exempel:

    @register.inclusion_tag("my_template.html")
    def my_tag(a, b, *args, **kwargs):
        warning = kwargs["warning"]
        profile = kwargs["profile"]
        ...
        return ...

I mallen kan sedan valfritt antal argument, åtskilda med mellanslag, skickas till malltaggen. Precis som i Python anges värdena för nyckelordsargument med hjälp av likhetstecknet (”`=`”) och måste anges efter de positionella argumenten. Till exempel:

    {% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}

### Avancerade anpassade malltaggar

Ibland räcker det inte med de grundläggande funktionerna för att skapa egna malltaggar. Oroa dig inte, Django ger dig fullständig tillgång till de interna funktioner som krävs för att bygga en malltagg från grunden.

### En snabb översikt

Mallsystemet fungerar i en tvåstegsprocess: kompilering och rendering. När du definierar en anpassad malltagg anger du hur kompileringen och renderingen ska gå till.

When Django compiles a template, it splits the raw template text into nodes. Each node is an instance of `django.template.Node` and has a `render()` method. A compiled template is a list of `Node` objects. When you call `render()` on a compiled template object, the template calls `render()` on each `Node` in its node list, with the given context. The results are all concatenated together to form the output of the template.

För att definiera en anpassad malltagg anger du alltså hur den råa malltaggen konverteras till en `Node` (kompileringsfunktionen) och vad nodens `render()`-metod gör.

### Skriva sammanställningsfunktionen

För varje malltagg som mallparsern stöter på anropar den en Python-funktion med taggens innehåll och själva parserobjektet. Denna funktion är ansvarig för att returnera en `Node`-instans baserat på innehållet i taggen.

Låt oss till exempel skriva en fullständig implementering av vår malltagg, `{% current_time %}`, som visar aktuellt datum/tid, formaterat enligt en parameter som anges i taggen, i `strftime()`-syntax. Det är en bra idé att bestämma taggens syntax före allt annat. I vårt fall kan vi säga att taggen ska användas så här:

    <p>The time is {% current_time "%Y-%m-%d %I:%M %p" %}.</p>

Parsern för denna funktion ska ta parametern och skapa ett `Node`-objekt:

    from django import template

    def do_current_time(parser, token):
        try:
            # split_contents() knows not to split quoted strings.
            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])

Anteckningar:

- `parser` är mallens parserobjekt. Vi behöver det inte i det här exemplet.

- `token.contents` är en sträng med taggens råa innehåll. I vårt exempel är det `'current_time "%Y-%m-%d %I:%M %p"'`.

- Metoden `token.split_contents()` separerar argumenten på mellanslag medan citerade strängar hålls ihop. Den mer okomplicerade `token.contents.split()` skulle inte vara lika robust, eftersom den naivt skulle dela på alla mellanslag, inklusive de inom citerade strängar. Det är en bra idé att alltid använda `token.split_contents()`.

- Denna funktion är ansvarig för att skapa `django.template.TemplateSyntaxError`, med användbara meddelanden, för alla syntaxfel.

- The `TemplateSyntaxError` exceptions use the `tag_name` variable. Don’t hardcode the tag’s name in your error messages, because that couples the tag’s name to your function. `token.contents.split()[0]` will always be the name of your tag – even when the tag has no arguments.

- Funktionen returnerar en `CurrentTimeNode` med allt noden behöver veta om den här taggen. I det här fallet skickar den argumentet – `"%Y-%m-%d %I:%M %p"`. De inledande och avslutande citaten från malltaggen tas bort i `format_string[1:-1]`.

- Parsningen är på mycket låg nivå. Django-utvecklarna har experimenterat med att skriva små ramverk ovanpå detta parsningssystem, med hjälp av tekniker som EBNF-grammatiker, men dessa experiment gjorde mallmotorn för långsam. Den är på låg nivå för att det är snabbast.

### Skriva renderingsprogrammet

Det andra steget i att skriva egna taggar är att definiera en subklass av `Node` som har en `render()`-metod.

Om vi fortsätter med exemplet ovan måste vi definiera `CurrentTimeNode`:

    import datetime
    from django import template

    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)

Anteckningar:

- `__init__()` hämtar `format_string` från `do_current_time()`. Skicka alltid eventuella alternativ/parametrar/argument till en `Node` via dess `__init__()`.

- Det är i metoden `render()` som det egentliga arbetet sker.

- `render()` bör i allmänhet misslyckas tyst, särskilt i en produktionsmiljö. I vissa fall dock, särskilt om `context.template.engine.debug` är `True`, kan denna metod ge upphov till ett undantag för att göra felsökningen enklare. Till exempel ger flera kärntaggar upphov till `django.template.TemplateSyntaxError` om de får fel antal eller typ av argument.

I slutändan resulterar denna frikoppling av kompilering och återgivning i ett effektivt mallsystem, eftersom en mall kan återge flera kontexter utan att behöva analyseras flera gånger.

### Överväganden om automatisk eskapning

Utdata från malltaggar körs inte automatiskt genom filtren för automatisk eskapning (med undantag för `simple_tag()` som beskrivs ovan). Det finns dock fortfarande ett par saker som du bör tänka på när du skriver en malltagg.

Om `render()`-metoden i din malltagg lagrar resultatet i en kontextvariabel (i stället för att returnera resultatet i en sträng), bör den se till att anropa `mark_safe()` om så är lämpligt. När variabeln slutligen renderas kommer den att påverkas av den auto-escape-inställning som gäller vid den tidpunkten, så innehåll som bör vara säkert från ytterligare escaping måste markeras som sådant.

Om din malltagg skapar en ny kontext för att utföra en underrendering, ska du också ställa in auto-escape-attributet till den aktuella kontextens värde. Metoden `__init__` för klassen `Context` tar en parameter som heter `autoescape` som du kan använda för detta ändamål. Till exempel:

    from django.template import Context

    def render(self, context):
        # ...
        new_context = Context({"var": obj}, autoescape=context.autoescape)
        # ... Do something with new_context ...

Detta är inte en särskilt vanlig situation, men det är användbart om du renderar en mall själv. Till exempel:

    def render(self, context):
        t = context.template.engine.get_template("small_fragment.html")
        return t.render(Context({"var": obj}, autoescape=context.autoescape))

Om vi hade försummat att skicka in det aktuella värdet för `context.autoescape` till vår nya `Context` i det här exemplet skulle resultaten alltid ha escapats automatiskt, vilket kanske inte är det önskade beteendet om malltaggen används i ett `{% autoescape off %}`-block.

### Säkerhetsöverväganden för trådar

När en nod har parsats kan dess `render`-metod anropas ett obegränsat antal gånger. Eftersom Django ibland körs i flertrådade miljöer kan en enda nod samtidigt renderas med olika kontexter som svar på två separata förfrågningar. Därför är det viktigt att se till att dina malltaggar är trådsäkra.

För att se till att dina malltaggar är trådsäkra bör du aldrig lagra tillståndsinformation på själva noden. Django tillhandahåller till exempel en inbyggd `cycle` malltagg som cyklar mellan en lista med givna strängar varje gång den återges:

    {% for o in some_list %}
        <tr class="{% cycle 'row1' 'row2' %}">
            ...
        </tr>
    {% endfor %}

En naiv implementation av `CycleNode` kan se ut ungefär så här:

    import itertools
    from django import template

    class CycleNode(template.Node):
        def __init__(self, cyclevars):
            self.cycle_iter = itertools.cycle(cyclevars)

        def render(self, context):
            return next(self.cycle_iter)

Men anta att vi har två mallar som renderar mallutdraget från ovan samtidigt:

1. Tråd 1 utför sin första loopiteration, `CycleNode.render()` returnerar ’row1’

2. Tråd 2 utför sin första loopiteration, `CycleNode.render()` returnerar ’row2’

3. Tråd 1 utför sin andra loopiteration, `CycleNode.render()` returnerar ’row1’

4. Tråd 2 utför sin andra loopiteration, `CycleNode.render()` returnerar ’row2’

CycleNode itererar, men den itererar globalt. När det gäller tråd 1 och tråd 2 returnerar den alltid samma värde. Detta är inte vad vi vill!

För att lösa detta problem tillhandahåller Django en `render_context` som är associerad med `contexten` för den mall som för närvarande renderas. `render_context` beter sig som en Python-dictionary och bör användas för att lagra `Node`-tillstånd mellan anrop av `render`-metoden.

Låt oss omarbeta vår `CycleNode`-implementering för att använda `render_context`:

    class CycleNode(template.Node):
        def __init__(self, cyclevars):
            self.cyclevars = cyclevars

        def render(self, context):
            if self not in context.render_context:
                context.render_context[self] = itertools.cycle(self.cyclevars)
            cycle_iter = context.render_context[self]
            return next(cycle_iter)

Observera att det är helt säkert att lagra global information som inte kommer att ändras under hela `Node` liv som ett attribut. I fallet med `CycleNode`, `cyclevars` argumentet ändras inte efter `Node` är instantiated, så vi behöver inte sätta det i `render_context`. Men tillståndsinformation som är specifik för den mall som för närvarande återges, som den aktuella iterationen av `CycleNode`, bör lagras i `render_context`.

Observera

Observera hur vi använde `self` för att omfatta den `CycleNode` specifika informationen inom `render_context`. Det kan finnas flera `CycleNodes` i en given mall, så vi måste vara försiktiga så att vi inte stjäl en annan nods tillståndsinformation. Det enklaste sättet att göra detta är att alltid använda `self` som nyckel till `render_context`. Om du håller reda på flera tillståndsvariabler kan du göra `render_context[self]` till en ordbok.

### Registrering av taggen

Slutligen, registrera taggen med din moduls `Library` instans, som förklaras i skriva egna malltaggar ovan. Exempel:

    register.tag("current_time", do_current_time)

Metoden `tag()` tar två argument:

1. Namnet på malltaggen - en sträng. Om detta utelämnas kommer namnet på kompileringsfunktionen att användas.

2. Kompileringsfunktionen – en Python-funktion (inte namnet på funktionen som en sträng).

Precis som med filterregistrering är det också möjligt att använda detta som en dekorator:

    @register.tag(name="current_time")
    def do_current_time(parser, token): ...

    @register.tag
    def shout(parser, token): ...

Om du utelämnar argumentet `name`, som i det andra exemplet ovan, kommer Django att använda funktionens namn som taggnamn.

### Överföring av mallvariabler till taggen

Även om du kan skicka valfritt antal argument till en malltagg med hjälp av `token.split_contents()`, packas alla argument upp som stränglitteraler. Det krävs lite mer arbete för att skicka dynamiskt innehåll (en mallvariabel) till en malltagg som ett argument.

Medan de tidigare exemplen har formaterat den aktuella tiden till en sträng och returnerat strängen, anta att du vill skicka in en `DateTimeField` från ett objekt och låta malltaggen formatera datumtiden:

    <p>This post was last updated at {% format_time blog_entry.date_updated "%Y-%m-%d %I:%M %p" %}.</p>

Initialt kommer `token.split_contents()` att returnera tre värden:

1. Taggens namn `format_time`.

2. Strängen `'blog_entry.date_updated'` (utan omgivande citattecken).

3. Formateringssträngen `'"%Y-%m-%d %I:%M %p"'`. Returvärdet från `split_contents()` kommer att inkludera inledande och avslutande citattecken för stränglitteraler som denna.

Nu bör din tagg börja se ut så här:

    from django import template

    def do_format_time(parser, token):
        try:
            # split_contents() knows not to split quoted strings.
            tag_name, date_to_be_formatted, format_string = token.split_contents()
        except ValueError:
            raise template.TemplateSyntaxError(
                "%r tag requires exactly two arguments" % 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 FormatTimeNode(date_to_be_formatted, format_string[1:-1])

You also have to change the renderer to retrieve the actual contents of the `date_updated` property of the `blog_entry` object. This can be accomplished by using the `Variable()` class in `django.template`.

För att använda klassen `Variable`, instansiera den med namnet på den variabel som ska lösas och anropa sedan `variable.resolve(context)`. Så här gör du till exempel:

    class FormatTimeNode(template.Node):
        def __init__(self, date_to_be_formatted, format_string):
            self.date_to_be_formatted = template.Variable(date_to_be_formatted)
            self.format_string = format_string

        def render(self, context):
            try:
                actual_date = self.date_to_be_formatted.resolve(context)
                return actual_date.strftime(self.format_string)
            except template.VariableDoesNotExist:
                return ""

Variabelupplösning ger upphov till undantaget `VariableDoesNotExist` om den inte kan lösa upp den sträng som skickas till den i sidans aktuella kontext.

### Ställa in en variabel i kontexten

Exemplen ovan matar ut ett värde. I allmänhet är det mer flexibelt om dina malltaggar ställer in mallvariabler istället för att mata ut värden. På så sätt kan mallförfattare återanvända de värden som dina malltaggar skapar.

Om du vill ställa in en variabel i kontexten använder du dictionary assignment på kontextobjektet i metoden `render()`. Här är en uppdaterad version av `CurrentTimeNode` som ställer in en mallvariabel `current_time` istället för att skriva ut den:

    import datetime
    from django import template

    class CurrentTimeNode2(template.Node):
        def __init__(self, format_string):
            self.format_string = format_string

        def render(self, context):
            context["current_time"] = datetime.datetime.now().strftime(self.format_string)
            return ""

Observera att `render()` returnerar en tom sträng. `render()` bör alltid returnera strängar. Om allt malltaggen gör är att ställa in en variabel, ska `render()` returnera den tomma strängen.

Så här använder du den här nya versionen av taggen:

    {% current_time "%Y-%m-%d %I:%M %p" %}<p>The time is {{ current_time }}.</p>

Variabelns omfattning i sitt sammanhang

Alla variabler som anges i kontexten kommer endast att vara tillgängliga i samma `block` i mallen där de tilldelades. Det här beteendet är avsiktligt; det ger ett utrymme för variabler så att de inte står i konflikt med kontexten i andra block.

But, there’s a problem with `CurrentTimeNode2`: The variable name `current_time` is hardcoded. This means you’ll need to make sure your template doesn’t use `{{ current_time }}` anywhere else, because the `{% current_time %}` will blindly overwrite that variable’s value. A cleaner solution is to make the template tag specify the name of the output variable, like so:

    {% current_time "%Y-%m-%d %I:%M %p" as my_current_time %}
    <p>The current time is {{ my_current_time }}.</p>

För att göra det måste du refaktorisera både kompileringsfunktionen och `Node`-klassen, så här:

    import re

    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 ""

    def do_current_time(parser, token):
        # This version uses a regular expression to parse tag contents.
        try:
            # Splitting by None == splitting by spaces.
            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)

Skillnaden här är att `do_current_time()` hämtar formatsträngen och variabelnamnet och skickar båda till `CurrentTimeNode3`.

Slutligen, om du bara behöver ha en enkel syntax för din anpassade kontextuppdaterande malltagg, kan du överväga att använda `simple_tag()`-genvägen, som stöder tilldelning av taggresultaten till en mallvariabel.

### Parsning tills en annan blocktagg

Malltaggar kan fungera tillsammans. Till exempel döljer standardtaggen `{% comment %}` allt fram till `{% endcomment %}`. För att skapa en malltagg som den här använder du `parser.parse()` i din kompileringsfunktion.

Så här kan en förenklad `{% comment %}` tagg implementeras:

    def do_comment(parser, token):
        nodelist = parser.parse(("endcomment",))
        parser.delete_first_token()
        return CommentNode()

    class CommentNode(template.Node):
        def render(self, context):
            return ""

Observera

Den faktiska implementeringen av `{% comment %}` är något annorlunda i det att den tillåter brutna malltaggar att visas mellan `{% comment %}` och `{% endcomment %}`. Detta görs genom att anropa `parser.skip_past('endcomment')` istället för `parser.parse(('endcomment',)))` följt av `parser.delete_first_token()`, vilket gör att en nodlista inte genereras.

`parser.parse()` takes a tuple of names of block tags to parse until. It returns an instance of `django.template.NodeList`, which is a list of all `Node` objects that the parser encountered before it encountered any of the tags named in the tuple.

I `"nodelist = parser.parse(('endcomment',))"` i exemplet ovan är `nodelist` en lista över alla noder mellan `{% comment %}` och `{% endcomment %}`, exklusive `{% comment %}` och `{% endcomment %}` själva.

När `parser.parse()` har anropats har parsern ännu inte ”konsumerat” taggen `{% endcomment %}`, så koden måste uttryckligen anropa `parser.delete_first_token()`.

`CommentNode.render()` returnerar en tom sträng. Allt mellan `{% comment %}` och `{% endcomment %}` ignoreras.

### Parsning till en annan blocktagg och lagring av innehåll

I det föregående exemplet kastade `do_comment()` bort allt mellan `{% comment %}` och `{% endcomment %}`. Istället för att göra det är det möjligt att göra något med koden mellan blocktaggar.

Här är till exempel en anpassad malltagg, `{% upper %}`, som skriver allt mellan sig själv och `{% endupper %}` med stor bokstav.

Användning:

    {% upper %}This will appear in uppercase, {{ your_name }}.{% endupper %}

Precis som i det föregående exemplet använder vi `parser.parse()`. Men den här gången skickar vi den resulterande `nodelist` till `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()

Det enda nya konceptet här är `self.nodelist.render(context)` i `UpperNode.render()`.

För fler exempel på komplex rendering, se källkoden för `{% for %}` i django/template/defaulttags.py och `{% if %}` i django/template/smartif.py.