Penyesuaian etiket cetakan dan saringan

Cetakan bahasa Django datang dengan beragam luas dari built-in tags and filters 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 %}.

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, 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:

{% load poll_extras %}

Aplikasi yang mengandung penyesuaian etiket harus berada dalam INSTALLED_APPS agar etiket {% 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 %} 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. 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, 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:

{{ 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 dan penyaring dan zona waktu 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. 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.

    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().

    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)
    

    The needs_autoescape flag and the autoescape keyword argument mean that our function will know whether automatic escaping is in effect when the filter is called. We use autoescape to decide whether the input data needs to be passed through django.utils.html.conditional_escape or not. (In the latter case, we use the identity function as the "escape" function.) The conditional_escape() function is like escape() except it only escapes input that is not a SafeData instance. If a SafeData instance is passed to conditional_escape(), the data is returned unchanged.

    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.

Peringatan

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 dan 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:

{{ comment|urlize_and_linebreaks }}

akan setara dengan:

{{ comment|urlize|linebreaksbr }}

Saringan dan zona waktu

Jika anda menulis sebuah penyesuaian penyaring yang dijalankan pada obyek 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.

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() 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() jika anda sepenuhnya yakin bahwa kode anda tidak mengandung kerentanan XSS. Untuk membangun potongan kecil HTML, gunakan 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.

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:

{% 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:

{% 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. Kami akan menggunakan etiket seperti ini:

{% show_results poll %}

...dan keluaran akan kelihatan seperti ini:

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

Next, create the template used to render the tag's output. This template is a fixed feature of the tag: the tag writer specifies it, not the template designer. Following our example, the template is very short:

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

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:

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

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

{% 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:

{% 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.

For example, let's write a full implementation of our template tag, {% current_time %}, that displays the current date/time, formatted according to a parameter given in the tag, in strftime() syntax. It's a good idea to decide the tag syntax before anything else. In our case, let's say the tag should be used like this:

<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.
  • The function returns a CurrentTimeNode with everything the node needs to know about this tag. In this case, it passes the argument -- "%Y-%m-%d %I:%M %p". The leading and trailing quotes from the template tag are removed in 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() seperti yang digambarkan diatas). Bagaimanapun, masih terdapat sepasang hal anda harus ingat ketika menulis sebuah cetakan etiket.

If the render() method of your template tag stores the result in a context variable (rather than returning the result in a string), it should take care to call mark_safe() if appropriate. When the variable is ultimately rendered, it will be affected by the auto-escape setting in effect at the time, so content that should be safe from further escaping needs to be marked as such.

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 %}.

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 siap pakai yang berputar terhadap sebuah daftar dari deretan karakter yang diberikan setiap kali dia dibangun:

{% 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'

The CycleNode is iterating, but it's iterating globally. As far as Thread 1 and Thread 2 are concerned, it's always returning the same value. This is not what we want!

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.

Catatan

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 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 dari sebuah obyek dan mempunyai bentuk etiket cetakan yang tanggal-waktu:

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

To set a variable in the context, use dictionary assignment on the context object in the render() method. Here's an updated version of CurrentTimeNode that sets a template variable current_time instead of outputting it:

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:

{% 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:

{% 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(), yang mendukung menetapkan hasil etiket ke cetakan variabel.

Menguraikan sampai blok etiket lain

Cetakan etiket dapat bekerja dalam berduaan. Sebagai contoh, standar etiket {% 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 ''

Catatan

Penerapan sebenarnya dari {% 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 contoh 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 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:

{% 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 %} dalam django/template/defaulttags.py dan {% if %} di django/template/smartif.py.

Back to Top