---
title: "Penyesuaian etiket cetakan dan saringan"
version: 2.0
locale: id
source: https://docs.djangoproject.com/id/2.0/howto/custom-template-tags/
canonical: https://djangodocs.dev/id/2.0/howto/custom-template-tags/
---
# Penyesuaian etiket cetakan dan saringan

Cetakan bahasa Django datang dengan beragam luas dari [built-in tags and filters](/id/2.0/ref/templates/builtins/) dirancang untuk megnalamatkan kebutuhan pembawaan logis dari aplikasi anda. Namun, anda mungkin menemukan diri anda sendiri membutuhkan fungsi yang tidak dicakupi oleh inti kumpulan dari cetakan primitif. Anda dapat memperpanjang cetakan mesin dengan menentukan penyesuaian etiket dan penyaring menggunakan Python, dan membuat mereka tersedua pada cetakan anda menggunakan etiket [`{% load %}`](/id/2.0/ref/templates/builtins/#std-templatetag-load).

## Tata letak kode

Tempat paling umum untuk menentukan cetakan etiket dan penyaring adalah didalam aplikasi Django. Jika mereka terhubung pada aplikasi yang ada, dia masuk akal untuk menggabungkan mereka; kalau tidak, mereka dapat ditambahkan ke aplikasi baru. Ketika aplikasi Django ditambahkan ke [`INSTALLED_APPS`](/id/2.0/ref/settings/#std-setting-INSTALLED_APPS), etiket apapun dia tentukan dalam tempat biasa dibawah otomatis dibuat tersedia untuk dimuat dengan cetakan.

Aplikasi harus mengandung sebuah direktori `templatetags`, pada tingkatan sama seperti `models.py`, `views.py`, dll. Jika ini tidak ada, buat dia - jangan lupakan berkas `__init__.py` untuk memastikan direktori diperlakukan sebagai paket Python.

> **Peladen pengembangan tidak akan otomatis nyala kembali**
>
> Setelah menambahkan modul  `templatetags`, anda akan butuh memulai kembali peladen anda sebelum anda dapat menggunakan etiket atau penyaring dalam cetakan.

Penyesuaian etiket dan penyaring anda akan berada dalam sebuah modul didalam direktori `templatetags`. Nama dari berkas modul adalah nama anda akan gunakan untuk memuat etiket kemudian, jadi berhati-hatilah untuk mengambil sebuah nama yang tidak akan bentrok dengan penyesuaian etiket dan penyaring di aplikasi lain.

Sebagai contoh, jika penyesuaian etiket/penyaring anda berada dalam sebuah berkas yang dipanggil `poll_extras.py`, tata letak eplikasi anda mungkin terlihat seperti ini:

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

Dan di cetakan anda, anda akan menggunakan berikut:

```html+django
{% load poll_extras %}
```

Aplikasi yang mengandung penyesuaian etiket harus berada dalam [`INSTALLED_APPS`](/id/2.0/ref/settings/#std-setting-INSTALLED_APPS) agar etiket [`{% load %}`](/id/2.0/ref/templates/builtins/#std-templatetag-load) bekerja. Ini adalah fitur keamanan: Dia mengizinkan anda untuk menempatkan kode Python untuk banyak cetakan pustaka pada sebuah mesin rumah tunggal tanpa mengadakan akses ke semua dari mereka untuk setiap pemasangan Django.

Tidak ada batasan pada bagaimana banyak modul anda taruh dalam paket `templatetags`. Hanya perlu diingat bahwa sebuah pernyataan [`{% load %}`](/id/2.0/ref/templates/builtins/#std-templatetag-load) akan memuatetiket/penyaring untuk nama modul Python yang diberikan, bukan nama dari aplikasi.

Untuk menjadi pustaka sah, modul harus mengandung sebuah variabel tingkat-modul yang dinamai `register` yaitu sebuah instance `template.Library`, dimana semua etiket dan penyaring didaftarkan. Jadi dekat diatas modul anda, taruh berikut:

```
from django import template

register = template.Library()
```

Kalau tidak, cetakan modul etiket dapat didaftarkan melalui argumen `'libraries'` ke [`DjangoTemplates`](/id/2.0/topics/templates/#django.template.backends.django.DjangoTemplates). Ini berguna jika anda ingin menggunakan label berbeda dari nama cetakan modul etiket ketika memuat cetakan etiket. Dia juga mengadakan anda untuk mendaftar etiket tanpa memasang sebuah aplikasi.

> **Dibalik layar**
>
> Untuk banyak contoh, baca kode sumber untuk penatingan dan etiket awal Django. Mereka berada di `django/template/defaultfilters.py` dan `django/template/defaulttags.py`, masing-masing.
>
> Untuk informasi lebih pada etiket [`load`](/id/2.0/ref/templates/builtins/#std-templatetag-load), baca dokumentasinya.

## Menulis saringan cetakan penyesuaian

Saringan penyesuaian hanyalah fungsi fungsi yang mengambil satu atau dua argumen:

- Nilai dari variabel (masukan) -- tidak perlu deretan karakter.
- Nilai dari argumen -- ini dapat mempunyai nilai awal, atau ditinggalkan sama sekali.

Sebagai contoh, dalam saringan `{{ var|foo:"bar" }}`, the filter `foo` akan dilewatkan variabel `var` dan argumen `''bar''`.

Sejak cetakan bahasa tidak menyediakan penanganan pengecualian, setiap pengecualian yang muncul dari sebuah cetakan saringan akan tidak terlindungi sebagai sebuah kesalahan peladen. Dengan demikian, fungsi penyaring harus menghindari memunculkan pengecualian jika ada alasan nilai mundur untuk kembali. Dalam kasus dari masukan yang mewakili kesalahan jelas dalam sebuah cetakan, menampilkan pencegualian mungkin masih lebih baik daripada kegagalan diam yang menyembunyikan kesalahan.

Ini adalah sebuah contoh pengertian saringan

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

Dan ini adalah sebuah contoh bagaimana saringan itu akan digunakan:

```html+django
{{ somevariable|cut:"0" }}
```

Kebanyakan saringan tidak mengambil argumen. Dalam kasus ini, tinggalkan argumen dari fungsi anda. Contoh:

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

### Mendaftarkan saringan penyesuaian

#### `django.template.Library.filter()`

Sekali anda telah menulis arti saringan anda, anda butuh mendaftarkannya dengan instance `Library` anda, untuk membuatnya tersedia pada bahasa cetakan Django:

```
register.filter('cut', cut)
register.filter('lower', lower)
```

Cara `Library.filter()` mengambil dua argumen:

1. Nama dari saringan -- sebuah deretan karakter.
2. Fungsi himpunan -- Sebuah fungsi Python (bukan nama fungsi sebagai deretan karakter).

Anda dapat menggunakan `register.filter()` sebagai penghias:

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

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

Jika anda tidak mengubah argumen `name`, seperti di contoh kedua di atas, Django akan menggunakan nama fungsi sebagai nama filter.

Akhirnya, `register.filter()` juga menerima tiga argumen katakunci, `is_safe`, `needs_autoescape`, dan `expects_localtime`. Argumen ini digambarkan dalam [penyaring dan pelolosan otomatis](#filters-auto-escaping) dan [penyaring dan zona waktu](#filters-timezones) dibawah.

### Cetakan penyaring yang mengharapkan deretan karakter

#### `django.template.defaultfilters.stringfilter()`

Jika anda sedang menulis sebuah cetakan penyaring yang hanya menerima sebuah deretan karakter sebagai argumen pertama, anda harus menggunakan decorator `stringfilter`. Ini akan merubah sebuah obyek ke nilai deretan karakternya sebelum dilewatkan ke fungsi anda:

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

register = template.Library()

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

Jalan ini, anda akan dapat melewatkan, katakan, sebuah integer ke penyaring ini, dan dia tidak menyebabkan sebuah `AttributeError` (karena integer tidak mempunyai cara `lower()`).

### Penyaring dan pelolosan otomatis

When writing a custom filter, give some thought to how the filter will interact
with Django's auto-escaping behavior. Note that two types of strings can be
passed around inside the template code:

- **Raw strings** are the native Python strings. On output, they're escaped if
  auto-escaping is in effect and presented unchanged, otherwise.
- **Safe strings** adalah deretan karakter yang telah ditandai aman dari pelolosan lanjut pada waktu keluar. Apa saja yang diperlukan pelolosan telah dilakukan. Mereka umumnya digunakan untuk keluaran yang mengandung HTML mentah yang akan di ditafsirkan apa adanya pada sisi klien.

  Internally, these strings are of type
  [`SafeText`](/id/2.0/ref/utils/#django.utils.safestring.SafeText). You can test for them
  using code like:

  ```
  from django.utils.safestring import SafeText

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

Kode cetakan penyaring gagal kedalam satu dari dua keadaan:

1. Penyaring anda tidak memperkenalkan salah satu karakter tidak aman (`<`, `>`, `'`, `"` atau `&`) kedalam hasil yang tidak hadir. Dalam kasus ini, anda dapat membiarkan Django mengambil semua penanganan pelolosan otomatis untuk anda. Semua anda butuh untuk dilakukan adalah menyetel bendera `is_safe` ke `True` ketika anda mendaftar fungsi penyaringan, seperti begitu:

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

   Bendera ini memberitahu Django bahwa jika sebuah deretan karakter "safe" dilewatkan kedalam penyaring anda, hasil akan masih menjadi "safe" dan jika sebuah deretan karakter tidak aman dilewatkan, Django akan otomatis meloloskannya, jika diperlukan.

   Anda dapat berpikir dari ini sebagai berarti "penyaring ini aman -- dia tidak memperkenalkan kemungkinan apapun dari HTML tidak aman."

   The reason `is_safe` is necessary is because there are plenty of
   normal string operations that will turn a `SafeData` object back into
   a normal `str` object and, rather than try to catch them all, which would
   be very difficult, Django repairs the damage after the filter has completed.

   Sebagi contoh, andaikan anda mempunyai sebuah penyaring yang menambahkan deretan karakter `xx` ke akhir dari masukan. Sejak ini diperkenalkan karakter HTML tidak berbahaya ke hasil (selain dari apapun itu sudah hadir), anda harus menandai penyaring anda dengan `is_safe`:

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

   Ketika penyaring ini digunakan dalam sebuah cetakan dimana pelolosan otomatis diadakan, Django akan meloloskan keluaran ketika masukan belum ditandai sebagai "safe".

   Secara awalan, `is_safe` adalah `False`, dan anda dapat menghilangkannya dari saringan dimana dia tidak wajib.

   Hati-hati ketika memutuskan jika penyaring anda sangat membiarkan deretan karakter aman sebagai safe. Jika anda sedang *memidnahkan* karakter, anda mungkin secara tidak sengaja membiarkan etiket HTML tidak seimbang atau kesatuan dalam hasil. Sebagai contoh, memindahkan sebuah `>` dari masukan mungkin mengubah `<a>` menjadi `<a`, yang akan butuh diloloskan pada keluaran untuk menghindari masalah. Demikian pula, memindahkan titik koma (`;`) dapat merubah `&amp;` into `&amp`, yang tidak lagi kesatuan sah dan demikian butuh pelolosan. Kebanyakan kasus tidak dekat dengan trik ini, tetapi jaga mata anda untuk masalah apapun seperti itu ketika meninjau kode anda.

   Menandai sebuah penyaring `is_safe` akan memaksa nilai kembalian penyaring menjadi deretan karakter. Jika penyaring anda harus mengembalikan sebuah boolean atau nilai bukan deretan karakter, tandai dia `is_safe` akan mungkin mempunyai konsekuensi yang tidak diinginkan (seperti merubah boolean False menjadi deretan karakter 'False').
2. Kalau tidak, kode penyaring anda dapat secara manual merawat pelolosan apapun yang dibutuhkan. Ini adalah penting ketika anda sedang memperkenalkan markah HTML baru kedalam hasil. Anda ingin menandai keluaran sebagai aman dari pelolosan lebih lanjut sehingga markah HTML anda tidak diloloskan lebih lanjut, jadi anda akan butuh menangani masukan diri anda sendiri.

   Untuk menandai keluaran sebagai deretan karakter aman, gunakan [`django.utils.safestring.mark_safe()`](/id/2.0/ref/utils/#django.utils.safestring.mark_safe).

   Berhati-hatilah, meskipun. Anda butuh melakukan lagi daripada hanya menandai keluaran sebagai aman. Anda butuh memastikan dia sangat *adalah* aman, dan apa yang anda lakukan tergantung pada apakah pelolosan otomatis berpengaruh. Idenya adalah menulis penyaring yang dapat berjalan dalam cetakan dimana pelolosan otomatis salah satunya nyala atau mati agar membuat hal-hal semakin mudah untuk cetakan penulis anda.

   Agar penyaring anda mengetahui keadaan pelolosan otomatis saat ini, setel bendera `needs_autoescape` ke `True` ketika anda mendaftar fungsi penyaring anda. (Jika anda tidak menentukan bendera ini, dia awalnya adalah `False`). Bendera ini mengatakan Django bahwa fungsi penyaring anda ingin dilewatkan sebuah argumen katakunci tambahan, dipanggil `autoescape`, yaitu `True` jika pelolosan otomatis dalam efek dan `False` sebaliknya. Sangat dianjurkan untuk menyetel parameter `autoescape` ke `True`, sehingga jika anda memanggil fungsi dari kode Python dia akan mempunyai pelolosan diadakan secara awal.

   Sebagai contoh, mari kita menulis penyaring yang menekankan karakter pertama dari sebuah deretan karakter:

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

   Bendera `needs_autoescape` dan argumen katakunci `autoescape` berarti bahwa fungsi kami akan mengetahui apakah pelolosan otomatis berada dalam efek ketika penyaring dipanggil. Kami menggunakan `autoescape` untuk memutuskan apakah data masukan butuh dilewatkan melalui `django.utils.html.conditional_escape` atau tidak. (Dalam kasus terakhir, kami hanya menggunakan fungsi identitas sebagai fungsi "escape".) Fungsi `conditional_escape()` seperti `escape()` kecuali dia hanya meloloskan masukan yang **bukan** instance `SafeData`. Jika sebuah instance `SafeData` dilewatkan ke `conditional_escape()`, data dikembalikan tidak berubah.

   Akhirnya, dalam contoh diatas, kami ingat untuk menandai hasil sebagai aman sehingga HTML kami dimasukkan langsung kedalam cetakan tanpa pelolosan lanjut.

   Tidak perlu khawatir tentang bendera `is_safe` dalam kasus ini (meskipun termasuknya tidak akaan melukai apapun). Kapanpun anda secara manual menangani masalah pelolosan otomatis dan mengembalikan sebuah deretan karakter aman, bendera `is_safe` tidak merubah apapun.

> **Warning**
>
> Menghindari kerentanan XSS ketika menggunakan ulang filter bawaan
>
> Penyaring siap pakai Django mempunyai `autoescape=True` secara awal agar mendapatkan kebiasaan pelolosan otomatis yang benar dan menghindari kerentanan cross-site script.
>
> Dalam versi lama Django, berhati-hatilah ketika menggunakan penyaring pasang-tetap Django sebagai `autoescape` nilai awal ke `None`. Anda akan butuh melewatkan `autoescape=True` untuk mendapatkan pelolosan otomatis.
>
> Sebagai contoh, jika anda ingin menulis penyesuaian penyaring dipanggil `urlize_and_linebreaks` yang menggabungkan penyaring [`urlize`](/id/2.0/ref/templates/builtins/#std-templatefilter-urlize) dan [`linebreaksbr`](/id/2.0/ref/templates/builtins/#std-templatefilter-linebreaksbr), penyaring akan kelihatan seperti:
>
> ```
> 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
>     )
> ```
>
> Kemudian:
>
> ```html+django
> {{ comment|urlize_and_linebreaks }}
> ```
>
> akan setara dengan:
>
> ```html+django
> {{ comment|urlize|linebreaksbr }}
> ```

### Saringan dan zona waktu

Jika anda menulis sebuah penyesuaian penyaring yang dijalankan pada obyek [`datetime`](https://docs.python.org/3/library/datetime.html#datetime.datetime), anda akan biasanya mendaftarkannya dengan bendera `expects_localtime` disetel ke `True`:

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

Ketika bendera ini disetel, jika argumen pertama pada penyaring anda adalah zona waktu perhatikan datetime, Django akan merubahnya ke zona waktu saat ini sebelum melewatkannya ke penyaring anda ketika sesuai, menurut pada [aturan untuk perubahan zona waktu dalam cetakan](/id/2.0/topics/i18n/timezones/#time-zones-in-templates).

## Menulis etiket cetakan penyesuaian

Etiket lebih rumit daripada penyaring, karena etiket dapat melakukan apapun. Django menyediakan sejumlah jalan pintas yang membuat penulisan kebanyakan jenis etiket lebih mudah. Pertama kami akan menjelajahi jalan pintas tersebut, kemudian menjelaskan bagaimana menulis sebuah etiket dari goresan untuk kasus-kasus tersebut ketika jalan pintas tidak cukup kuat.

### Etiket sederhana

#### `django.template.Library.simple_tag()`

Banyak cetakan etiket mengambil sejumlah argumen -- deretan karakter atau cetakan variabel -- dan mengembalikan sebuah hasil setelah melakukan beberapa pengolahan berdasarkan hanya pada masukan argumen dan beberapa informasi luar. Sebagai contoh, sebuah etiket `current_time` mungkin menerima sebuah bentuk deretan karakter dan mengembalikan waktu sebagai bentuk deretan karakter.

Untuk meringankan pembuatan dari jenis ini dari etiket, Django menyediakan fungsi pembantu, `simple_tag`. Fungsi ini, yaitu sebuah cara dari `django.template.Library`, mengambil sebuah fungsi yang menerima angka apapun dari argumen, membungkusnya dalam fungsi `render` dan bit-bit yang dibutuhkan lainnya disebutkan diatas dan mendaftarkannya dengan cetakan sistem.

Fungsi `current_time` kami dapat jadi ditulis seperti ini:

```
import datetime
from django import template

register = template.Library()

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

Beberapa hal untuk dicatat tentang fungsi pembantu `simple_tag`:

- Memeriksa angka wajib dari argumen, dll., telah selesai dilakukan oleh waktu fungsi kami dipanggil, jadi kami tidak butuh melakukan itu.
- Kutipan disekeliling argumen (jika ada) telah dilucuti, jadi kami hanya menerima deretan karakter kosong.
- Jika argumen adalah cetakan variabel, fungsi kami dilewatkan nilai sast ini dari variabel, bukan variabel itu sendiri.

Tidak seperti peralatan etiket lain, `simple_tag` melewatkan keluarannya melalui [`conditional_escape()`](/id/2.0/ref/utils/#django.utils.html.conditional_escape) jika konteks cetakan dalam suasana pelolosan otomatis, untuk memastikan HTML benar dan melindungi anda dari kerentanan XSS.

Jika tambahan pelolosan tidak diharapkan, anda akan butuh menggunakan [`mark_safe()`](/id/2.0/ref/utils/#django.utils.safestring.mark_safe) jika anda sepenuhnya yakin bahwa kode anda tidak mengandung kerentanan XSS. Untuk membangun potongan kecil HTML, gunakan [`format_html()`](/id/2.0/ref/utils/#django.utils.html.format_html) daripada `mark_safe()` sangat kuat dianjurkan.

Jika etiket cetakan anda butuh untuk mengakses konteks saat ini, anda dapat menggunakan argumen `takes_context` ketika mendaftarkan etiket anda:

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

Catat bahwa argumen pertama *harus* dipanggil `context`.

Untuk informasi lebih pada bagaimana pilihan `takes_context` bekerja, lihat bagian pada [inclusion tags](#howto-custom-template-tags-inclusion-tags).

Jika anda butuh menamai kembali etiket anda, anda dapat menyediakan nama penyesuaian untuknya:

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

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

Fungsi `simple_tag` mungkin menerima angka apapun dari argumen terkait atau kata kunci. Sebagai contoh:

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

Kemudian dalam cetakan angka apapun dari argumen, dipisahkan oleh spasi, mungkin dilewatkan ke cetakan etiket. Seperti dalam Python, nilai untuk argumen katakunci disetel menggunakan tanda sama dengan ("`=`") dan harus disediakan setelah penempatan argumen. Sebagai contoh:

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

Itu dimungkinkan untuk menyimpan hasil etiket dalam sebuah cetakan variabel daripada secara langsung mengeluarkannya. Ini dilakukan dengan menggunakan argumen `as` diikuti oleh nama variabel. Melakukannya mengadakan anda untuk mengeluarkan isi diri anda dimana anda melihatnya cocok:

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

### Penyertaaan etiket

#### `django.template.Library.inclusion_tag()`

Jenis umum lainnya dari cetakan etiket adalah jenis yang menampilkan beberapa data dengan membangun cetakan *lain*. Sebagai contoh, antarmuka admin Django menggunakan penyesuaian etiket cetakan untuk menampilkan tombol dibawah dari halaman formulir "add/change". Tombol-tombol tersebut selalu terlihat sama, tetapi ssaran tautan berubah tergantung pada obyek yang sedang disunting -- sehingga mereka kasus sempurna untuk menggunakan cetakan kecil yang diisi dengan rincian dari obyek saat ini. (Dalam kasus admin, ini adalah etiket `submit_row`.)

Urutan etiket ini dipanggil "penyertaan etiket".

Menulis pencatuman etiket adalah mungkin pertunjukan terbaik dengan contoh. Mari kita menulis sebuah etiket yang mengeluarkan daftar pilihan untuk obyek `Poll` yang diberikan, seperti dibuat dalam [tutorial](/id/2.0/intro/tutorial02/#creating-models). Kami akan menggunakan etiket seperti ini:

```html+django
{% show_results poll %}
```

...dan keluaran akan kelihatan seperti ini:

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

Pertama, tentukan fungsi yang mengambil argumen dan menghasulkan kamus dari data untuk hasil. Titik terpenting disini adalah kami hanya butuh mengembalikan sebuah kamus, bukan apapun lebih rumit. Ini akan digunakan sebagai konteks cetakan untuk fragmen cetakan. Contoh:

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

Selanjutnya, buat cetakan digunakan untuk membangun keluaran etiket. Cetakan ini adalah fitur tetap dari etiket: penulis etiket menentukannya, bukan perancang etiket. Mengikuti contoh kami, cetakan sangat mudah:

```html+django
<ul>
{% for choice in choices %}
    <li> {{ choice }} </li>
{% endfor %}
</ul>
```

Sekarang, buat dan daftar penyertaan etiket dengan memanggil cara `inclusion_tag()` pada sebuah obyek `Library`. Mengikuti contoh kami, jika cetakan diatas dalam sebuah berkas dipanggil `results.html` dalam sebuah direktori yang dicari oleh pemuat cetakan, kami akan mendaftar etiket seperti ini:

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

Kemungkinan lain dia memungkinkan mendaftarkan pemasukan etiket menggunakan instace [`django.template.Template`](/id/2.0/ref/templates/api/#django.template.Template):

```
from django.template.loader import get_template
t = get_template('results.html')
register.inclusion_tag(t)(show_results)
```

...ketika pertama membuat fungsi.

Terkadang, penyertaan etiket anda mungkin membutuhkan sejumlah besar argumen, membuatnya sakit untuk cetakan penulis melewatkan semua argumen dan mengingat urutan mereka. Untuk mengatasi ini, Django menyediakan sebuah pilihan `takes_context` untuk penyertaan etiket. Jika anda menentukan `takes_context` dalam sebuah cetakan etiket, etiket tidak akan mempunyai argumen wajib, dan pokok fungsi Python akan mempunyai satu argumen -- konteks cetakan mulai ketika etiket dipanggil.

Sebagai contoh, katakan anda sedang menulis sebuah penyertaan etiket yang akan selalu digunakan dalam sebuah konteks yang mengandung variabel `home_link` dan `home_title` yang menunjuk kembali ke halaman utama. Disini fungsi Python akan kelihatan seperti:

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

Catat bahwa parameter pertama pada fungsi *harus* dipanggil `context`.

Dalam baris `register.inclusion_tag()` itu, kami mencirikan `takes_context=True` dan nama dari cetakan. Ini adalah cetakan `link.html` mungkin kelihatan seperti:

```html+django
Jump directly to <a href="{{ link }}">{{ title }}</a>.
```

Lalu, kapanpun anda ingin menggunakan tag tersuai, muat pustakanya dan panggil tanpa argument, seperti:

```html+django
{% jump_link %}
```

Catat bahwa ketika anda menggunakan `takes_context=True`, tidak perlu melewatkan argumen ke etiket cetakan. Dia otomatis mendapatkan akses ke konteks.

Parameter `takes_context` awalnya adalah `False`. Ketika disetel ke `True`, etiket melewatkan obyek konteks, seperti dalam contoh ini. Itu hanya oerbedaan diantara kasus ini dan contoh `inclusion_tag` sebelumnya.

Fungsi `inclusion_tag` mungkin menerima angka apapun dari penempatan atau argumen kata kunci. Sebagai contoh:

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

Kemudian dalam cetakan angka apapun dari argumen, dipisahkan oleh spasi, mungkin dilewatkan ke cetakan etiket. Seperti dalam Python, nilai untuk argumen katakunci disetel menggunakan tanda sama dengan ("`=`") dan harus disediakan setelah penempatan argumen. Sebagai contoh:

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

### Etiket cetakan penyesuaian tingkat lanjut

Terkadang fitur dasar untuk pembuatan etiket cetakan penyesuaian tidak cukup. Jangan khawatir, Django memberikan anda akses lengkap ke internal yang diwajibkan untuk membangun sebuah etiket cetakan dari bawah ke atas.

### Ikhtisar cepat

Sistem cetakan bekerja dalam dua langkah pengolahan: menyusun dan membangun. Untuk menentukan etiket cetakan penyesuaian, anda menentukan bagaimana penyusunan bekerja dan bagaimana pembangunan bekerja.

Ketika Django menyusun sebuah cetakan, dia memisahkan teks cetakan mentah kedalam ''nodes''. Setiap node adalah sebuah instance dari `django.template.Node` dan mempunyai sebuah cara `render()`. Sebuah cetakan tersusun adalah, sederhana, sebuah daftar dari obyek `Node`. Ketika anda memanggil `render()` pada sebuah obyek cetakan disusun, cetakan memanggil `render()` pada setiap `Node` dalam daftar nodenya, dengan konteks yang diberikan. Hasil adalah semua digabungkan bersama untuk membentuk keluaran dari cetakan.

Jadi, untuk menentukan penyesuaian cetakan etiket, anda menentukan bagaimana cetakan etiket mentah dirubah kedalam `Node` (fungsi penyusunan), dan apa cara `render()` node lakukan.

### Menulis fungsi himpunan

Untuk setiap etiket cetakan pertemuan pengurai cetakan, dia memanggil fungsi Python dengan isi etiket dan pengurai obyek itu sendiri. Fungsi ini bertanggungjawab untuk mengembalikan sebuah instance `Node` berdasarkan pada isi dari etiket.

Sebagai contoh, mari kita menulis penerapan lengkap dari cetakan etiket sederhana kami, `{% current_time %}`, yang menampilkan tanggal/waktu saat ini, dibentuk menurut sebuah parameter yang diberikan dalam etiket, di sintaksis [`strftime()`](https://docs.python.org/3/library/time.html#time.strftime). Adalah ide bagus untuk memutuskan etiket sintaksis sebelum yang lainnya. Dalam kasus kami, mari kita katakan etiket harus digunakan seperti ini:

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

Pengurai untuk fungsi ini harus mengambil parameter dan membuat obyek `Node`:

```
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])
```

Catatan:

- parser\` adalah obyek pengurai cetakan. Kami tidak membutuhkannya dalam contoh ini.
- `token.contents` adalah deretan karakter dari isi mentah dari etiket. Dalam contoh kami, dia adalah `'current_time "%Y-%m-%d %I:%M %p"'`.
- Cara `token.split_contents()` memisahkan argumen pada ruang selama menjaga deretan karakter dikutip bersama. Lebih mudah `token.contents.split()` tidak akan menjadi kuat, karena akan naif pisah pada *semua* ruang, termasuk itu dalam deretan karakter dikutip. Adalah ide bagus untuk selalu menggunakan `token.split_contents()`.
- Fungsi ini bertanggungjawab untuk memunculkan `django.template.TemplateSyntaxError`, dengan pesan membantu, untuk sintakses kesalahan apapun.
- Pengecualian `TemplateSyntaxError` menggunakan variabel `tag_name`. Jangan mengkode keraskan nama etiket dalam pesan-pesan kesalahan anda, karena itu memasangkan nama etiket ke fungsi anda. `token.contents.split()[0]` akan ''selalu'' menjadi nama dari etiket anda -- bahkan ketika etiket tidak mempunyai argumen.
- Fungsi mengembalikan sebuah `CurrentTimeNode` dengan semuanya yang node butuhkan untuk mengetahui tentang etiket ini. Dalam kasus ini, dia hanya melewatkan argumen -- `"%Y-%m-%d %I:%M %p"`. Awalan dan akhiran kutipan dari cetakan etiket dipindahkan dalam `format_string[1:-1]`.
- Penguraian adalah tingkat-rendah. Pengembang Django mempunyai percobaan dengan menulis kerangka kecil diatas dari sistem penyurai ini, menggunakan teknik seperti tata bahasa EBNF, tetapi percobaan tersebut membuat cetakan mesin terlalu lambat. Dia adalah tingkat-rendah karena itu tercepat.

### Menulis pembangun

Langkah kedua dalam menulis penyesuaian etiket adalah menentukan sebuah subkelas `Node` yang mempunyai cara `render()`.

Melanjutkan contoh diatas, kami butuh menentukan `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)
```

Catatan:

- `__init__()` mendapatkan `format_string` dari `do_current_time()`. Selalu melewati options/parameters/arguments apapun ke `Node` melalui `__init__()` nya.
- Cara `render()` adalah dimana pekerjaan sebenarnya terjadi.
- `render()` harus pada umumnya gagal secara diam, khususnya dalam lingkungan produksi. Dalam beberapa kasus bagaimanapun, khususnya jika `context.template.engine.debug` adalah `True`, cara ini mungkin memunculkan sebuah pengecualian untuk membuat mencari kesalahan lebih mudah. Sebagai contoh, beberapa etiket inti memunculkan `django.template.TemplateSyntaxError` jika mereka menerima nomor atau jenis salah dari argumen

Akhirnya, pemisahan ini dari hasil penyusunan dan pembangunan dalam sebuah sistem cetakan efesien, karena sebuah cetakan dapat membangun banyak konteks tanpa harus diuraikan banyak kali.

### Pertimbangan pelolosan otomatis

Keluaran dari cetakan etiket *tidak\** otomatis berjalan melalui penyaring pelolosan otomatis (dengan pencegualian dari [`simple_tag()`](#django.template.Library.simple_tag) seperti yang digambarkan diatas). Bagaimanapun, masih terdapat sepasang hal anda harus ingat ketika menulis sebuah cetakan etiket.

Jika fungsi `render()` dari cetakan anda menyimpan hasil dalam sebuah konteks variabel (daripada mengembalikan hasil dalam sebuah deretan karakter), dia harus berhati-hati untuk memanggil `mark_safe()` jika sesuai. Ketika variabel pada akhirnya dibangun, dia akan dipengaruhi oleh pengaturan pelolosan otomatis dalam efek pada saat itu, sehingga isi yang harus aman dari pelolosan lebih lanjut butuh ditandai saja.

Juga, jika cetakan etiket anda membuat konteks baru untuk melakukan beberapa sub pembangunan, setel atribut pelolosan otomatis ke nilai konteks saat ini. Cara `__init__` untuk kelas `Context` mengambil parameter dipanggil `autoescape` dimana anda dapat menggunakan untuk tujuan ini. Sebagai contoh:

```
from django.template import Context

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

Ini bukan situasi paling umum, tetapi ini berguna jika anda membangun cetakan anda sendiri. Sebagai contoh:

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

Jika kami mempunyai terlantar untuk dilewatkan dalam nilai `context.autoescape` saat ini ke `Context` baru kami dalam contoh ini, hasil akan mempunyai *selalu* otomatis diloloskan, yang mungkin perilaku yang tidak diinginkan jika cetakan etiket digunakan dalam sebuah blok [`{% autoescape off %}`](/id/2.0/ref/templates/builtins/#std-templatetag-autoescape).

### Pertimbangan Thread-safety

Sekali node diuraikan, cara `render` dia mungkin dipanggil sejumlah kali. Sejak Django terkadang berjalan dalam lingkungan banyak-rangkaian, sebuah node tunggal mungkin secara berkelanjutan membangun dengan konteks berbeda dalam menjawab ke dua permintaan terpisah. Oleh sebab itu, sangat penting untuk memastikan cetakan etiket anda adalah aman.

Untuk memastikan cetakan etiket anda thread safe, anda harus jangan menyimpan informasi keadaan pada node itu sendiri. Sebagai contoh, Django menyediakan cetakan etiket [`cycle`](/id/2.0/ref/templates/builtins/#std-templatetag-cycle) siap pakai yang berputar terhadap sebuah daftar dari deretan karakter yang diberikan setiap kali dia dibangun:

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

Sebuah penerapan yang dibuat-buat dari `CycleNode` mungkin kelihatan kurang lebih seperti ini:

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

Tetapi, seandainya kami mempunyai dua cetakan membangun potongan cetakan dari atas pada waktu bersamaan:

1. Urutan 1 melakukan pengulangan pertamanya, `CycleNode.render()` mengembalikan 'row1'
2. Urutan 2 melakukan pengulangan pertamanya, `CycleNode.render()` mengembalikan 'row2'
3. Urutan 1 melakukan pengulangan keduanya, `CycleNode.render()` mengembalikan 'row1'
4. Urutan 2 melakukan pengulangan keduanya, `CycleNode.render()` mengembalikan 'row2'

CycleNode berulang-ulang, tetapi perulangannya secara umum. Sejauh Thread 1 dan Thread 2 dirisaukan, dia selalu mengembalikan nilai sama. Ini jelas bukan apa kami inginkan!

Untuk mengalamatkan masalah ini, Django menyediakan sebuah `render_context` yang terhubung dengan `context` dari cetakan yang saat ini sedang dibangun. `render_context` berperilaku seperti sebuah kamus Python, dan harus digunakan untuk menyimpan keadaan `Node` diantara permohonan dari cara `render`.

Mari kita refactor penerapan `CycleNode` kami untuk menggunakan `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)
```

Catat bahwa ini sangat aman untuk menyimpan informasi umum yang tidak akan merubah keseluruhan hidup dari `Node` sebagai sebuah atribut. Dalam kasus `CycleNode`, argumen `cyclevars` tidak berubah setelah `Node` dipakai, jadi kami tidak butuh menaruhnya ke dalam `render_context`. Tetapi keadaan informasi yang khusus pada cetakan yang saat ini sedang dibangun, seperti putaran saat ini dari `CycleNode`, harus disimpan dalam `render_context`.

> **Note**
>
> Perhatikan bagaimana kami menggunakan `self` untuk menjangkau `CycleNode` informasi tertentu dalam `render_context`. Disana mungkin banyak `CycleNodes` dalam cetakan yang diberikan, jadi kami butuh berhati-hati tidak megnkritik informasi keadaan node lainnya. Cara termudah melakukan ini adalah selalu menggunakan `self` sebagai kunci kedalam `render_context`. Jika anda sedang menjaga jalur dari beberapa variabel keadaan, buat `render_context[self]` sebuah kamus.

### Mendaftarkan etiket

Akhirnya, daftarkan etiket dengan instance `Library` modul, sesuai penjelasan dalam [menulis penyesuaian cetakan penyaring](#howto-writing-custom-template-tags) diatas. Contoh:

```
register.tag('current_time', do_current_time)
```

Cara `tag()` mengambil dua argumen:

1. Nama dari cetakan etiket -- sebuah deretan karakter. Jika ini dihilangkan dari fungsi penyusunan akan digunakan.
2. Fungsi himpunan -- Sebuah fungsi Python (bukan nama fungsi sebagai deretan karakter).

Dengan pendaftaran penyaring, dia juga memungkinkan menggunakan ini sebagai decorator:

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

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

Jika anda meninggalkan argumen `name`, seperti dalam contoh kedud diatas, Django akan menggunakan nama fungsi sebagai nama etiket.

### Melewatkan variabel cetakan ke etiket

Meskipun anda dapat melewatkan angka apapun dari argumen ke etiket cetakan menggunakan `token.split_contents()`, argumen semua tidak dipaketkan sebagai harfiah string. Sedikit kerja lebih diwajibkan untuk melewatkan isi dinamis (variabel cetakan) ke etiket cetakan sebagai sebuah argumen.

Selagi contoh sebelumnya telah membentuk waktu saat ini ke dalam string dan mengembalikan string, misalkan anda ingin melewatkan di [`DateTimeField`](/id/2.0/ref/models/fields/#django.db.models.DateTimeField) dari sebuah obyek dan mempunyai bentuk etiket cetakan yang tanggal-waktu:

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

Mulanya, `token.split_contents()` akan mengembalikan tiga nilai:

1. Nama etiket `format_time`.
2. String `'blog_entry.date_updated'` (tanpa dikelilingi kutip).
3. Pembentukan deretan karakter `'"%Y-%m-%d %I:%M %p"'`. Mengembalikan nilai dari `split_contents()` akan menyertakan kutipan awal dan akhir untuk deretan karakter persis seperti ini.

Sekarang etiket anda harus kelihatan seperti ini:

```
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])
```

Anda juga mempunyai kesemparan pembangun untuk mengambil isi sebenarnya dari sifat `date_updated` dari obyek `blog_entry`. Ini dapat diselesaikan dengan menggunakan kelas `Variable()` dalam `django.template`.

Untuk menggunakan kelas `Variable`, berikan contoh dia dengan nama dari variabel untuk di selesaikan dan kemudian panggil `variable.resolve(context)`. Jadi, sebagai contoh:

```
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 ''
```

Keputusan variabel akan melempar sebuah pengecualian `VariableDoesNotExist` jika dia tidak dapat menyelesaikan deretan karakter dilewatkan ke dia dalam konteks saat ini dari halaman.

### Menyetel sebuah variabel di konteks

Contoh diatas mengeluarkan sebuah nilai. Umumnya, dia lebih fleksibel jika cetakan etiket anda mengatur cetakan variabel daripada mengeluarkan nilai. Cara tersebut, penulis cetakan dapat menggunakan nilai yang cetakan etiket anda buat.

Untuk mengatur sebuah variabel dalam konteks, cukup gunakan penugasan kamus pada obyek konteks dalam cara `render()`. Ini adalah versi terperbaharui dari `CurrentTimeNode` yang mengatur sebuah variabel cetakan `current_time` daripada mengeluarkannya:

```
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 ''
```

Catat bahwa `render()` mengembalikan deretan karakter kosong. ```render() harus selalu mengembalikan keluaran deretan karakter. Jika semua cetakan etiket disetel variabel, ``render()``` harus mengembalikan deretan karakter kosong.

Ini adalah bagaimana anda akan menggunakan versi baru dari etiket:

```html+django
{% current_time "%Y-%M-%d %I:%M %p" %}<p>The time is {{ current_time }}.</p>
```

> **Lingkup variabel di konteks**
>
> Apapun kumpulan variabel dalam konteks akan hanya tersedua dalam `block` yang sama dari cetakan dimana dia ditetapkan. Perilaku ini adalah disengaja; dia menyediakan cakupan untuk variabel sehingga mereka tidak bertentangan dengan konteks di blok lainnya.

Tetapi, ada masalah dengan `CurrentTimeNode2`: Nama variabel `current_time` adalah dikode keraskan. Ini berarti anda akan butuh untuk memastikan cetakan anda tidak menggunakan `{{ current_time }}` dimanapun juga, karena `{% current_time %}` akan membabi buta meniban nilai variabel itu. Sebuah solusi pembersih adalah membuat cetakan etiket menentukan nama dari variabel keluaran, seperti begitu:

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

Untuk melakukannya, anda akan butuh me refaktor kedua fungsi penyusun dan kelas `Node`, seperti begitu:

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

Perbedaan disini adalah bahwa `do_current_time()` mengambil bentuk deretan karakter dan nama variabel, melewatkan keduanya ke `CurrentTimeNode3`.

Akhirnya, jika anda hanya butuh mempunyai sintaksis sederhana untuk penyesuaian pembaharuan-konteks cetakan etiket, pertimbangkan menggunakan jalan pintas [`simple_tag()`](#django.template.Library.simple_tag), yang mendukung menetapkan hasil etiket ke cetakan variabel.

### Menguraikan sampai blok etiket lain

Cetakan etiket dapat bekerja dalam berduaan. Sebagai contoh, standar etiket [`{% comment %}`](/id/2.0/ref/templates/builtins/#std-templatetag-comment) menyembunyikan semua sampai `{% endcomment %}`. Untuk membuat sebuah cetakan etiket seperti ini, gunakan `parser.parse()` dalam fungsi penyusun anda

Ini adalah bagaimana menyederhanakan etiket `{% comment %}` mungkin diterapkan:

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

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

> **Note**
>
> Penerapan sebenarnya dari [`{% comment %}`](/id/2.0/ref/templates/builtins/#std-templatetag-comment) adalah sedikit berbeda dalam memungkinkan merusak cetakan etiket untuk muncul diantara `{% comment %}` dan `{% endcomment %}`. Dia melakukannya dengan memanggil `parser.skip_past('endcomment')` daripada `parser.parse(('endcomment',))` diikuti oleh `parser.delete_first_token()`, jadi menghindari generasi dari sebuah daftar node.

`parser.parse()` mengambil sebuah tuple dari nama dari blok etiket "untuk diurai sampai". Dia mengembalikan sebuah instance dari `django.template.NodeList`, yaitu sebuah daftar dari semua obyek `Node` yang pengurai jumpai "sebelum" dia menjumpai etiket dinamai apapun dalam tuple.

Dalam `"nodelist = parser.parse(('endcomment',))"` di contoh atas, `nodelist` adalah sebuah daftar dari semua node diantara `{% comment %}` dan `{% endcomment %}`, bukan menghitung `{% comment %}` dan `{% endcomment %}` mereka sendiri.

Setelah `parser.parse()` dipanggil, pengurai belum "mengkonsumsi" etiket `{% endcomment %}`, jadi kode butuh  secara eksplisit memanggil `parser.delete_first_token()`.

`CommentNode.render()` mengembalikan sebuah deretan karakter kosong. Apapun diantara `{% comment %}` dan `{% endcomment %}` diabaikan.

### Mengurai sampai blok etiket lainnya, dan menyimpan isi

Di contoh sebelumnya, `do_comment()` menyingkirkan apapun diantara `{% comment %}` and `{% endcomment %}`. Daripada melakukan itu, dimungkinkan untuk melakukan sesuatu dengan kode diantara blok etiket.

Sebagai contoh, ini adalah penyesuaian cetakan etiket, `{% upper %}`, yang menghuruf besarkan semuanya diantara dia sendiri dan `{% endupper %}`.

Penggunaan:

```html+django
{% upper %}This will appear in uppercase, {{ your_name }}.{% endupper %}
```

Seperti di contoh sebelumnya, kami akan menggunakan `parser.parse()`. Tetapi kali ini, kami melewatkan hasil `nodelist` ke `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()
```

Satu-satunya konsep disini adalah `self.nodelist.render(context)` dalam `UpperNode.render()`.

Untuk lebih contoh dari pembangunan rumit, lihat kode sumber dari [`{% for %}`](/id/2.0/ref/templates/builtins/#std-templatetag-for) dalam `django/template/defaulttags.py` dan [`{% if %}`](/id/2.0/ref/templates/builtins/#std-templatetag-if) di `django/template/smartif.py`.
