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

Cetakan bahasa Django datang dengan beragam luas dari [built-in tags and filters](/id/6.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/6.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/6.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 etiket/saringan penyesuaian anda di sebuah berkas di sebut `poll_extras.py`, tata letak aplikasi anda mungkin terlihat seperti ini:

```text
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/6.0/ref/settings/#std-setting-INSTALLED_APPS) agar etiket [`{% load %}`](/id/6.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/6.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/6.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 contoh berton-ton, baca kode sumber untuk etiket dan saringan awalan Django. Mereka ada di [django/template/defaultfilters.py](https://github.com/django/django/blob/stable/6.0.x/django/template/defaultfilters.py) and [django/template/defaulttags.py](https://github.com/django/django/blob/stable/6.0.x/django/template/defaulttags.py), masing-masing.
>
> Untuk informasi lebih pada etiket [`load`](/id/6.0/ref/templates/builtins/#std-templatetag-load), baca dokumentasinya.

## Menulis saringan cetakan penyesuaian

Penyaring penyesuaian adalah fungsi Python 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 penyaring tidak mengambil argumen. Dalam kasus ini, tinggalkan argumen dari fungsi anda:

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

Ketika menulis penyesuaian penyaring, berikan beberapa pemikiran bagaimana penyaring akan berinteraksi dengan perilaku pelolosan-otomatis Django. Catat bahwa dua jenis dari string dapat dilewatkan didalam sekitar kode cetakan:

- **Raw strings** adalah string Python asli. Pada keluaran, mereka diloloskan jika pelolosan-otomatis dalam pengaruh dan dihadirkan tidak berubah, sebaliknya.
- **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.

  Secara internal, string ini adalah jenis [`SafeString`](/id/6.0/ref/utils/#django.utils.safestring.SafeString). Anda dapat mencoba mereka menggunakan kode seperti:

  ```
  from django.utils.safestring import SafeString

  if isinstance(value, SafeString):
      # 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."

   Alasan `is_safe` perlu adalah karena ada banyak tindakan-tindakan string biasa yang akan merubah sebuah obyek `SafeData` kembali ke obyek `str` biasa dan, daripada mencoba menangkap mereka semua, yang akan sangat sulit, Django memperbaiki kerusakan setelah penyaring lengkap.

   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.

   Marking a filter `is_safe` will coerce the filter's return value to
   a string. If your filter should return a boolean or other non-string
   value, marking it `is_safe` will probably have unintended
   consequences (such as converting a boolean False to the string
   '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/6.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 kata kunci `autoescape` berarti bahwa fungsi kami akan mengetahui apakah pelolosan otomatis berpengaruh ketika saringan dipanggil. Kami menggunakan `autoescape` untuk memutuskan apakah data masukan butuh dilewatkan melalui `django.utils.html.conditional_escape` atau tidak. (Dalam kasus terakhir, kami menggunakan fungsi penciri sebagai fungsi "escape".) Fungsi `conditional_escape()` seperti `escape()` kecuali itu hanya meloloskan masukan yang **not**  instance `SafeData`. Jika 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/6.0/ref/templates/builtins/#std-templatefilter-urlize) dan [`linebreaksbr`](/id/6.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/6.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 sekitar argumen (jika ada) telah dihilangkan, jadi kami menerima string polos.
- 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/6.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/6.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/6.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>
```

### Etiket blok sederhana

> **New in Django 5.2**

#### `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()`](#django.template.Library.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.

Sebagai contoh, etiket blok penyesuaian yang membuat sebuah grafik dapat terlihat seperti ini:

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

Argumen `content` mengandung apapun diantara etiket `{% chart %}` dan `{% endchart %}`:

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

Jika ada etiket cetakan lain atau variabel didalam blok `content`, mereka akan dibangun sebelum dilewatkan ke fungsi etiket. Pada contoh diatas, `request.user` akan diselesaikan ketika `render_chart` dipanggil.

Etiket blok ditutup dengan `end{name}` (sebagai contoh, `endchart`). Ini dapat disesuaikan dengan parameter `end_name`:

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

Dimana akan membutuhkan pengertian cetakan seperti ini:

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

beberapa hal untuk dicatat mengenai `simple_block_tag`:

- Argumen pertama harus dipanggil `content`, dan akan mengandung isi dari etiket cetakan sebagai string dibangun.
- Variabel dilewatkan ke etika tidak disertakan dalam membangun konteks dari isi, seperti saat menggunakan etiket `{% with %}`.

Seperti [simple\_tag](#howto-custom-template-tags-simple-tags), `simple_block_tag`:

- Sahkan jumlah dan kualitas dari argumen.
- Hapus kutipan dari argumen jika dibutuhkan.
- Loloskan keluaran yang sesuai.
- Mendukung melewatkan `takes_context=True` pada waktu pendaftaran untuk mengakses konteks. Catat bahwa dalam kasus ini, argumen pertama pada fungsi penyesuaian *harus* dipanggil `context`, dan `content` harus mengikuti.
- Mendukung menamai kembali etiket dengan melewatkan argumen `name` ketika mendaftar.
- Mendukung menerima angka apapun dari penempatan atau argumen kata kunci.
- Mendukung penyimpanan hasil dalam variabel cetakan menggunakan versi `as`.

> **Content Escaping**
>
> `simple_block_tag` berperilaku mirip `simple_tag` sehubungan dengan pelolosan otomatis. Untuk rincian pelolosan dan keamanan, mengacu pada `simple_tag`. Karena argumen `content` telah dibangun oleh Django, dia sudah diloloskan.

#### Sebuah contoh lengkap

Pertimbangkan etiket cetakan penyesuaian yang membangkitkan sebuah kotak pesan yang mendukung banyak tingkatan pesan dan isi diluar frase sederhana0. Ini dapat diterapkan menggunakan `simple_block_tag` sebagai berikut:

*`testapp/templatetags/testapptags.py`*

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

Ketika perpaduan dengan tampilan minimal dan cetakan sesuai, seperti ditampilkan disini:

*`testapp/views.py`*

```python
from django.shortcuts import render

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

*`testapp/templates/test.html`*

```html+django
{% 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 %}
```

HTML berikut dihasilkan sebagai keluaran dibangun:

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

### 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/6.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 etiket: penulis etiket menentukannya, bukan perancang cetakan. Mengikuti contoh kami, cetakan sangat pendek:

```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/6.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.

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.

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 bertanggung jawab untuk mengembalikan sebuah contoh `Node` berdasarkan pada isi dari etiket.

Sebagai contoh, mari kita menulis penerapan penuh dari etiket cetakan kami, `{% current_time %}`, yang menampilkan tanggal/waktu saat ini, dibentuk menurut parameter yang diberikan pada etiket, di sintaks [`strftime()`](https://docs.python.org/3/library/time.html#time.strftime). Adalah ide bagus untuk memutuskan sintak etiket sebelum yang lainnya. Pada kasus kamu, katakanlah 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.
- 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.
- Fungsi mengembalikan sebuah `CurrentTimeNode` dengan apapun node butuh diketahui tentang etiket ini. Dalam kasus ini, itu melewatkan argumen -- `"%Y-%m-%d %I:%M %p"`. Kutipan terkemuka dan tertinggal dari etiket cetakan 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 metode `render()` dari etiket cetakan anda menyimpan hasil dalam variabel konteks (daripada mengembalikan hasil dalam string), itu harus berhati-hati untuk memanggil `mark_safe()` jika pantas. Ketika variabel akhirnya dibangun, itu akan dipengaruhi oleh pengaturan pelolosan-otomatis pada saat itu, sehingga isi yang harusnya aman dari pelolosan lebih lanjut butuh ditandai begitu 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/6.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/6.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, tetapi berulang secara global. Sejauh Thread 1 dan Thread 2 terkait, itu selalu mengembalikan nilai yang sama. Ini bukan yang kita 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, daftar etiket dengan instance `Library` modul anda, seperti dijelaskan dalam [writing custom template tags](#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/6.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])
```

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

Untuk menggunakan kelas `Variable`, instansiasikan itu dengan nama dari variabel untuk diselesaikan, dan 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 mengengeluarkan nilai. Umumnya, itu lebih fleksibel jika etiket cetakan anda mensetel variabel cetakan daripada mengeluarkan nilai. Cara itu, penulis cetakan dapat menggunakan kembali nilai yang etiket cetakan anda buat.

Untuk mensetel variabel dalam konteks, gunakan penugasan dictionary pada obyek konteks di metode `render()`. Ini adalah versi terperbaharui dari `CurrentTimeNode` yang menyetel 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.

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:

```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/6.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/6.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()` 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.

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 string 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 contoh lainnya dari pembangunan kompleks, lihat sumber kode dari [`{% for %}`](/id/6.0/ref/templates/builtins/#std-templatetag-for) di [django/template/defaulttags.py](https://github.com/django/django/blob/stable/6.0.x/django/template/defaulttags.py) and [`{% if %}`](/id/6.0/ref/templates/builtins/#std-templatetag-if) di [django/template/smartif.py](https://github.com/django/django/blob/stable/6.0.x/django/template/smartif.py).
