Panggil fungsi python dari jinja2

144

Saya menggunakan jinja2, dan saya ingin memanggil fungsi python sebagai penolong, menggunakan sintaksis yang sama seperti jika saya memanggil makro. jinja2 tampaknya berniat mencegah saya membuat panggilan fungsi, dan bersikeras saya ulangi diri saya dengan menyalin fungsi ke dalam templat sebagai makro.

Apakah ada cara mudah untuk melakukan ini? Dan, apakah ada cara untuk mengimpor seluruh rangkaian fungsi python dan membuatnya dapat diakses dari jinja2, tanpa melalui banyak omong kosong (seperti menulis ekstensi)?

Lee
sumber

Jawaban:

223

Bagi mereka yang menggunakan Flask, letakkan ini di __init__.py:

def clever_function():
    return u'HELLO'

app.jinja_env.globals.update(clever_function=clever_function)

dan dalam template Anda menyebutnya dengan {{ clever_function() }}

John32323
sumber
dapatkah Anda melewati beberapa fungsi seperti ini?
ffghfgh
6
Dalam versi yang lebih baru (saya menggunakan Jinja2 2.9.6) sepertinya berfungsi lebih mudah. Gunakan fungsi seperti yang Anda gunakan variabel (berfungsi juga dalam situasi yang lebih kompleks):from jinja2 import Template ##newline## def clever_function(): ##newline## return "Hello" ##newline## template = Template("{{ clever_function() }}") ##newline## print(template.render(clever_function=clever_function))
Semjon Mössinger
1
Bahkan 8 tahun kemudian, jika Anda menggunakan Flask, ini sepertinya solusi yang lebih bersih daripada jawaban yang lebih baru. Dan untuk menjawab pertanyaan lama dari @ffghfgh, ya — Anda dapat melewati beberapa fungsi.
kevinmicke
133

Catatan: Ini khusus untuk Labu!

Saya tahu posting ini cukup lama, tetapi ada metode yang lebih baik untuk melakukan ini di versi Flask yang lebih baru menggunakan pemroses konteks.

Variabel dapat dengan mudah dibuat:

@app.context_processor
def example():
    return dict(myexample='This is an example')

Di atas dapat digunakan dalam template Jinja2 dengan Labu seperti:

{{ myexample }}

(Output mana This is an example)

Serta fungsi lengkap penuh:

@app.context_processor
def utility_processor():
    def format_price(amount, currency=u'€'):
        return u'{0:.2f}{1}'.format(amount, currency)
    return dict(format_price=format_price)

Di atas ketika digunakan seperti:

{{ format_price(0.33) }}

(Yang menampilkan harga input dengan simbol mata uang)

Atau, Anda dapat menggunakan filter jinja , yang dimasukkan ke dalam Flask. Misalnya menggunakan dekorator:

@app.template_filter('reverse')
def reverse_filter(s):
    return s[::-1]

Atau, tanpa dekorator, dan mendaftarkan fungsi secara manual:

def reverse_filter(s):
    return s[::-1]
app.jinja_env.filters['reverse'] = reverse_filter

Filter yang diterapkan dengan dua metode di atas dapat digunakan seperti ini:

{% for x in mylist | reverse %}
{% endfor %}
Liam Stanley
sumber
4
di mana seharusnya fungsi-fungsi ini ada, init, tampilan atau di mana saja?
knk
3
__init__.pydengan asumsi Anda menyatakan di flask.Flask(__name__)sana.
Liam Stanley
6
Down memilih sebagai pertanyaan yang diajukan tentang Jinja2 dan jawabannya khusus untuk Flask.
AJP
13
@AJP Secara teoritis menjawab pertanyaan itu. Ini adalah SATU cara untuk menyelesaikan masalah, asalkan Anda juga menggunakan Flask. Sedikit seperti semua pertanyaan JavaScript sering menjawab memberikan alternatif dengan atau tanpa jQuery atau pertanyaan tentang Python sering menjawab keduanya untuk Python2 dan 3. Pertanyaan itu tidak termasuk Flask. (tidak seperti pertanyaan tentang Py2 akan mengecualikan jawaban Py3). Jawaban ini membantu saya.
jeromej
2
Sangat membantu, dan hanya apa yang saya cari. Jinja2 adalah bagian dari kerangka kerja web, dan karena itu tidak sepenuhnya independen dari back-end. Saya bekerja di kedua Django dan Labu dengan Python dan posting ini, serta yang lain di sini relevan untuk saya. Mencoba untuk secara spesifik menentukan pertanyaan sama berbahayanya menurut saya dengan tidak jelas.
76

Saya pikir jinja sengaja membuatnya sulit untuk menjalankan python 'sewenang-wenang' dalam sebuah template. Ia mencoba untuk menegakkan pendapat bahwa logika yang kurang dalam template adalah hal yang baik.

Anda dapat memanipulasi namespace global dalam sebuah Environmentinstance untuk menambahkan referensi ke fungsi Anda. Itu harus dilakukan sebelum Anda memuat template apa pun. Sebagai contoh:

from jinja2 import Environment, FileSystemLoader

def clever_function(a, b):
    return u''.join([b, a])

env = Environment(loader=FileSystemLoader('/path/to/templates'))
env.globals['clever_function'] = clever_function
Rob Cowie
sumber
5
Saya menemukan ini juga - Anda dapat menambahkan modul menggunakan sesuatu seperti ini: import utils.helpers env.globals['helpers'] = utils.helpers
Lee
@Lee. Ya, Anda dapat 'menyuntikkan' ruang nama (modul), fungsi, instance kelas, dll. Berguna, tetapi tidak sefleksibel mesin templat lain seperti mako. Meski begitu, jinja memiliki poin bagus lainnya. Saya akan berterima kasih jika Anda menerima jawabannya jika itu membantu :)
Rob Cowie
melakukan trik untuk saya saat melakukan proyek mesin aplikasi saya (webapp2 dan jinja2). terima kasih
Sojan V Jose
@RobCowie setelah menambahkan clever_function ke kamus env.globals, bagaimana seseorang dapat memanggil fungsi dari templat.
Jorge Vidinha
1
Jadi, {{ clever_function('a', 'b') }}
Rob Cowie
41
from jinja2 import Template

def custom_function(a):
    return a.replace('o', 'ay')

template = Template('Hey, my name is {{ custom_function(first_name) }} {{ func2(last_name) }}')
template.globals['custom_function'] = custom_function

Anda juga dapat memberikan fungsi di bidang sesuai jawaban Matroskin

fields = {'first_name': 'Jo', 'last_name': 'Ko', 'func2': custom_function}
print template.render(**fields)

Akan menghasilkan:

Hey, my name is Jay Kay

Bekerja dengan Jinja2 versi 2.7.3

Dan jika Anda ingin seorang dekorator memudahkan menentukan fungsi pada template.globalsmemeriksa jawaban Bruno Bronosky

AJP
sumber
8
Mungkin karena Anda
menurunkan
13
@BorkoKovacev itu bukan alasan yang bagus. Saya hanya memilih 2 jawaban; jawaban yang tentang Flask daripada Jinja2. Jika mereka ingin mengedit jawaban mereka pada topik dan tentang Jinja2 saya akan memilih mereka.
AJP
Tx @BorkoKovacev :)
AJP
1
Saya membuat versi dekorator fungsi dari jawaban ini. Saat ini di bagian bawah dengan 0 suara
:,
2
@BrunoBronosky bagus. Beri suara :) ... berikan satu dekade lagi dan mungkin lebih tinggi daripada milikku: P ... tidak akan pernah bisa menangkap termos; P
AJP
25

Saya suka jawaban @ AJP . Saya menggunakannya kata demi kata sampai akhirnya memiliki banyak fungsi. Kemudian saya beralih ke dekorator fungsi Python .

from jinja2 import Template

template = '''
Hi, my name is {{ custom_function1(first_name) }}
My name is {{ custom_function2(first_name) }}
My name is {{ custom_function3(first_name) }}
'''
jinga_html_template = Template(template)

def template_function(func):
    jinga_html_template.globals[func.__name__] = func
    return func

@template_function
def custom_function1(a):
    return a.replace('o', 'ay')

@template_function
def custom_function2(a):
    return a.replace('o', 'ill')

@template_function
def custom_function3(a):
    return 'Slim Shady'

fields = {'first_name': 'Jo'}
print(jinga_html_template.render(**fields))

Fungsi hal yang baik miliki __name__!

Bruno Bronosky
sumber
1
Ini sangat keren. Ketika Anda membubuhi keterangan fungsi dalam python apakah itu secara otomatis meneruskan nama fungsi ke fungsi anotasi?
mutant_city
@mutant_city, ya. Baca tautan dekorator fungsi Python. Barang bagus!
Bruno Bronosky
1
@BrunoBronosky Demonstrasi yang sangat baik tentang penggunaan yang masuk akal dan bersih untuk dekorator python juga. Pos yang bagus!
dreftymac
1
Sungguh implementasi yang luar biasa!
Philippe Oger
16

Tidak pernah melihat cara sederhana di dokumen resmi atau di stack overflow, tetapi saya kagum ketika menemukan ini:

# jinja2.__version__ == 2.8
from jinja2 import Template

def calcName(n, i):
    return ' '.join([n] * i)

template = Template("Hello {{ calcName('Gandalf', 2) }}")

template.render(calcName=calcName)
# or
template.render({'calcName': calcName})
Matroskin
sumber
Jawaban ini sejauh ini adalah yang terbaik. Anda hanya meneruskan fungsi ke templat dengan cara yang persis sama dengan Anda memberikan nilai, setelah semua fungsi adalah warga negara kelas satu di python :)
Mark Kortink
8

Gunakan lambda untuk menghubungkan templat ke kode utama Anda

return render_template("clever_template", clever_function=lambda x: clever_function x)

Kemudian Anda dapat memanggil fungsi di dalam templat dengan mulus

{{clever_function(value)}}
Robert Onslow
sumber
1
Penggunaan cerdas fungsi lambda.
23
@odiumediae: Tidak. Sama sekali tidak perlu. Lewatkan saja fungsi handle itu sendiri: clever_function = clever_function
vezult
@vezult saya mengerti. Bagaimana saya bisa melewatkan itu? Terima kasih sudah membereskannya!
6

Untuk memanggil fungsi python dari Jinja2, Anda dapat menggunakan filter khusus yang berfungsi sama seperti global: http://jinja.pocoo.org/docs/dev/api/#writing-filters

Ini cukup sederhana dan bermanfaat. Dalam file myTemplate.txt, saya menulis:

{{ data|pythonFct }}

Dan dalam skrip python:

import jinja2

def pythonFct(data):
    return "This is my data: {0}".format(data)

input="my custom filter works!"

loader = jinja2.FileSystemLoader(path or './')
env = jinja2.Environment(loader=loader)
env.filters['pythonFct'] = pythonFct
result = env.get_template("myTemplate.txt").render(data=input)
print(result)
Ben9000RPM
sumber
5

apakah ada cara untuk mengimpor seluruh rangkaian fungsi python dan membuatnya dapat diakses dari jinja2?

Ya ada, Selain jawaban lain di atas, ini berfungsi untuk saya.

Buat kelas dan isi dengan metode terkait mis

class Test_jinja_object:

    def __init__(self):
        self.myvar = 'sample_var'

    def clever_function (self):
        return 'hello' 

Kemudian buat instance kelas Anda di fungsi tampilan Anda dan meneruskan objek yang dihasilkan ke template Anda sebagai parameter untuk fungsi render_template

my_obj = Test_jinja_object()

Sekarang di templat Anda, Anda dapat memanggil metode kelas di jinja seperti itu

{{ my_obj.clever_function () }}
Kudehinbu Oluwaponle
sumber
Cara yang Setara dan sedikit lebih sederhana: letakkan semua fungsi untuk templat dalam modul, impor modul itu dan tambahkan sebagai templat global. Modul adalah objek yang berisi fungsi :) (tetapi bukan metode - tidak perlu param self dan tidak ada kelas reqiured!)
Éric Araujo
@ ÉricAraujo Bagaimana jika saya hanya membutuhkan seperangkat fungsi dalam satu atau dua templat dan tidak semuanya. Juga, bagaimana jika saya memerlukan set fungsi python yang berbeda dalam template jinjas yang berbeda? Apakah Anda masih menganggap itu efektif untuk mengimpor semuanya sebagai templat global daripada menempatkannya sebagai metode di kelas dan hanya lulus kelas dengan metode yang Anda butuhkan.
Kudehinbu Oluwaponle
Untuk hanya menggunakan templat tertentu, saya akan menambahkan fungsi (atau modul yang berisi fungsi) hanya ke dict konteks templat yang diulang oleh pandangan yang menggunakan templat tersebut.
Éric Araujo
3

Untuk mengimpor semua fungsi bawaan Anda dapat menggunakan:

app.jinja_env.globals.update(__builtins__)

Tambahkan .__dict__setelah __builtins__jika ini tidak berhasil.

Berdasarkan jawaban John32323 .

Solomon Ucko
sumber
2

Jika Anda melakukannya dengan Django, Anda bisa meneruskan fungsi dengan konteks:

context = {
    'title':'My title',
    'str': str,
}
...
return render(request, 'index.html', context)

Sekarang Anda akan dapat menggunakan strfungsi dalam template jinja2

Jahid
sumber
1

Ada keputusan yang jauh lebih sederhana.

@app.route('/x')
def x():
    return render_template('test.html', foo=y)

def y(text):
    return text

Lalu, di test.html :

{{ y('hi') }}
Никита Шишкин
sumber
jinja2.exceptions.UndefinedError: 'y' tidak terdefinisi
lww
ya, karena seharusnya menggunakan foo di test.html
luckyguy73