Herkese merhaba,
Bugün biraz Flask’ın template motoru olan Jinja’dan bahsetmek ve basit kod blokları üzerinden alıştırmalar yapmak istiyorum. Umuyorum Flask öğrenmek isteyen kişilere faydalı olabilecek bir kaynak olur.
Jinja, öğremesi çok da zor olmayan bir söz dizimine sahip bir template motorudur ve doğrudan template içerisinde ayrı bir ortam olarak çalışır.
Diyelim aşağıdaki gibi bir html elementimiz var.
<div>
</div>
Jinja değişkenleri, yukardaki HTML blokunun hem etiket kısmında hem de iç html kısmında değer olarak kullanılabilir.
<div class="{{ 'jinja_stringi' }}">
{{ 'jinja_stringi' }}
</div>
Yukarda, Python ortamına ait bir değişkeni template’in içine yerleştirdik. Yani bir nevi print
ettik.
O halde, {{
karakterleriyle başlayıp, }}
karakterleriyle biten Jinja kod satırları yazılan içeriğin template’e yerleştirilmesini sağlar. Template ortamına göndermek istediğimiz değişkenleri, render_template
fonksiyonunun anahtar argümanları olarak yazabiliriz.
app.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def main():
return render_template("main.html", degisken="merhaba")
if __name__ == "__main__":
app.run()
templates/main.html
<div class="{{ 'jinja_stringi' }}">
{{ degisken }}
</div>
Peki, template’in içinde bir tanımlama yapmak isteseydik, ne yapmamız gerekirdi? Aşağıdaki ifadelere bakalım:
<div>
{% set degisken = "değer" %}
</div>
Evet, burada {%
ile başlayıp %}
ile biten ifadeler görüyoruz. Bu ifadeler genellikle aşağıdaki amaçlar için kullanılır:
-
template içinde bir değişken oluşturmak için
-
template içinde sorgu yapmak için
-
template içinde döngü kurmak için
-
template’i miras olarak başka bir templatte kullanmak için
-
template içine başka bir template’i aktarmak için
Yukarda, template içinde tanımladığımız degisken
bir Python değişkenidir ve ancak Python ortamından gelen verileri argüman olarak alabilir. Yani bir js
değişkenini buraya argüman olarak veremeyiz.
<div>
<script>
var x = "hello";
</script>
{% set y = x %}
</div>
Yukardaki yazım geçersizdir. Keşke geçerli olsaydı diyebilirsiniz ama js’nin ortamı ile jinja’nın çalıştığı ortamlar farklı ortamlar. Peki, biz Jinja ile js’nin iletişim kurmasını başka şekilde sağlayabilir miyiz? Elbette, birazdan göreceğiz. O halde, buradan anlıyoruz ki, js’de tanımladığımız herhangi bir nesneyi Jinja’da kullanamayız.
Peki bu degisken
sadece main.html
dosyasında mı tanımlıdır sadece? Başka bir html dosyamız olsa, orada bu degisken
'i kullanabilir miyiz?
Kısa cevap; duruma bağlı. Yani, degisken
'i main.html
'de tanımlamış olabiliriz, adı x.html
olan başka bir template ise, main.html
'yi miras olarak alıyorsa veya x.html
dosyasına main.html
dosyası aktarılmışsa, degisken
, x.html
'nin ortamına girmiş, dolasıyıyla x.html
templateinde de tanımlı olur. Diğer senaryoda, yani main.html
'nin, x.html
ile bağlantı kurmadığı durumda her bir değişken, tanımlandığı ortam dışında tanımsızdır.
Peki, Jinja’da global
değişken gibi bir kavram var mı? Evet var. Bunun için server
tarafına geri dönüp, jinja’nın ortam değişkenlerine bir yenisini eklememiz gerekir.
app.jinja_env.globals.update(degisken="hello")
Yukardaki ifade, oluşturacağımız bütün template’lerde degisken
ismiyle tanımlı ve değeri "hello"
olan bir Jinja değişkeni tanımlayacaktır. O halde, Jinja ortamına Python fonksiyonlarınızı veya başka dillerde yazmış olduğunuz fonksiyonları hem local
olarak hem de global
olarak taşıyabilirsiniz.
Mesela bir senaryoya göre, isinstance
fonksiyonuna ve Python’dan gelen render
isimli başka bir fonksiyona ihtiyaç duyabilirsiniz.
<div>
{% if isinstance(something, int) %}
{{ render(code) }}
{% elif isinstance(something, str) %}
{{ before_render(code) }}
{% endif %}
</div>
Bu arada, template ortamında sorguların nasıl yapıldığını görüyorsunuz. Bir döngü kurmamız gerektiği zaman, benzer ifadeleri kullanıyoruz. Örneğin diyelim server’dan gelen yorumları bir döngü kurarak template içerisine yerleştirdiğimiz bir senaryo var:
<div>
{% for comment in comments %}
<div>{{ comment.title }}</div>
<div>{{ comment.content }}</div>
{% endfor %}
</div>
Jinja, tekrar tekrar yazmak zorunda kalacağımız birçok ifadenin otomatik ve dinamik bir şekilde oluşturulmasını sağlar, özellikle js ile birlikte kullanıldığında.
app.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def main():
return render_template("main.html", something=range(100))
if __name__ == "__main__":
app.run()
static/js/main.js
function init_thing(parent, thing) {
var div = document.createElement("div");
div.innerHTML = thing;
document.getElementById(parent).append(div);
}
templates/main.html
<script type="text/javascript" src="{{ url_for('static', filename='js/main.js') }}"></script>
<div id="container">
{% if something %}
{% for thing in something %}
{% if thing %}
<script>
init_thing("container", "{{ thing }}");
</script>
{% endif %}
{% endfor %}
{% endif %}
</div>
Burada görmüş olduğunuz gibi, js
ortamına python
ortamından veri aktarıyoruz.
Bir python fonksiyonunu Jinja ile template ortamına aktarabilir, sonra da main.js
gibi bir dosyadan gelen ve template içinde çağrılan bir js fonksiyonuna, bu python fonksiyonunu argüman olarak vermek isteyebilirsiniz. Bunlar gibi birçok senaryo uygulanabilirdir. Ancak templatein içinde python’ı bir yere kadar kullanabilirsiniz. Mesela aşağıdaki gibi bir ifade kullanamayız.
<div>
{% set x = [i for i in range(10)] %}
</div>
Burada for
tanımsızdır. Çünkü şimdilik for
'u sadece block olarak oluşturabiliyoruz, açılan blokların da kapatılması gerekiyor. Benzer şekilde template ortamında lambda
, def
, :
gibi ifadeler de tanımsızdır. O halde, bazı ifadeleri eğer yapılabiliyorsa fonksiyonel programlamaya göre template ortamında yazabiliriz.
<div>
{% set x = list(range(10)) %}
</div>
Ancak zaten ihtiyaç duyacağınız fonksiyonu, local
veya global
değişken olarak ortama aktarabilirsiniz, yani serverden [i for i in range(100)]
ifadesini, herhangi bir identifier ile alabiliyoruz zaten.
Bu arada son paylaştığım html ifadelerinin düzgün çalışabilmesi için, Jinja ortamına list
ve range
fonksiyonlarını aktarmış olmamız gerekir. Yukarda, bu aktarma işlemini nasıl yapabileceğimizi görmüştük. Şimdi, gömülü bütün Python fonksiyonlarını jinja ortamına aktaralım isterseniz.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import importlib
from flask import Flask, render_template
app = Flask(__name__)
builtins = importlib.import_module("builtins")
new_globals = {
attr: getattr(builtins, attr)
for attr in filter(lambda i: callable(getattr(builtins, i)), dir(builtins))
}
app.jinja_env.globals.update(**new_globals)
@app.route("/")
def main():
return render_template("main.html")
if __name__ == "__main__":
app.run()
Yukardaki kodlar ile bütün gömülü Python fonksiyonlarını ve sınıflarını template ortamına aktardık. Üstte yazdığımız {% set x = list(range(10)) %}
ifadesi artık geçerli bir ifadedir.
Şimdi, varsayalım template’in dışından template’in içine bir html
içeriği yerleştirmek istiyorsunuz. Bir python fonksiyonunuz var, bu fonksiyon bir html içeriği oluşturuyor diyelim. Bir div içine bir html kodu yerleştirmek istediğimizde, bu ifadenin bir str
olarak değil de bir html
elementi olarak anlaşılması için jinja
değişkenini safe
parametresiyle template’e yerleştirmemiz gerekir.
Yani aşağıdaki ifade ile bir tane button
oluşturmuş olmayız, sayfanın içine <button>button</button>
yazısını yerleştirmiş oluruz sadece.
<div>
{{ "<button>button</button>" }}
</div>
Karakter dizisinin bir html elementi
olarak anlaşılması için safe
sözcüğünü kullanmamız gerekiyor demiştik.
<div>
{{ "<button>button</button>"|safe }}
</div>
İşte şimdi, safe
sayesinde, ebeveyni div
olan bir button
elementi tanımlamış olduk.
Yukarda ayrıca bir template’in içine başka bir template’i aktarmaktan, template miras almaktan vs. bahsetmiş ama bunlarla alakalı örnekler yapmamıştık.
Template Inheritance; bir templatin model olarak kullanılması ve bu modelin bazı yerlerinin özelleştirilebilir olmasıyla ilgilidir. Örneğin, model.html
isimli bir bir html sayfası ve bu modeli sayfasını şablon olarak kullanan birkaç html dosyası daha olsun.
model.html
<div>
{% block variable %}
{% endblock variable %}
<script>
fonksiyon("{{ x }}");
</script>
<div>
a_model.html
{% extends "model.html" %}
{% block variable %}
{% set x = "Model A" %}
{% endblock variable %}
b_model.html
{% extends "model.html" %}
{% block variable %}
{% set x = "Model B" %}
{% endblock variable %}
Yukardaki kodlarda yer alan ifadeleri biraz açıklamaya çalışayım. Burada, model.html
dosyası içinde, variable
isimli, içeriği başka bir template tarafından değiştirilebilen bir blok oluşturduk. Sonra, a_model.html
ve b_model.html
templatelerinin içeriğini de model.html
templatini miras alarak oluşturduk. Miras alma işlemi esnasında variable
'i, override
ettik, yani yeniden tanımladık. variable
bloku içinde de x
isimli bir jinja
değişkeni oluşturduk. Böylece hem model.html
içinde yer alan x
tanımsız olmaktan kurtuldu hem de, farklı modellere göre farklı değerler alabilecek hale geldi.
extends
ifadesi template içinde yalnızca bir kez kullanılabilir. İkinci defa extends
'e ihtiyaç duyacağımız durumlarda, template importing
denilen bir kavramla karşılaşırız. Biraz bu kavramdan bahsedelim. Template importing
de template inheritance
'a benzer ancak, miras almada, miras alınan templatin belirli yerleri block
kullanılarak değiştirilebilir. import
ettiğimiz templatelerde ise bunu yapamayız. Ancak bir template içine birçok template’i aktarabiliriz. import
etme işlemini include
deyimi ile sağlıyoruz.
{% extends "model.html" %}
{% block variable %}
{% include "test.html" %}
{% endblock variable %}
include
deyiminin kullanımı yukardaki gibidir. test.html
'yi template içine aktarmadan tanımlamış olduğumuz her değişken, test.html
'de de tanımlı olacaktır. include
'dan sonra yazılanlar ise tanımsız olacaklar.
{% extends "parent.html" %}
{% set x = "test.html'de tanımlı" %}
{% include "test.html" %}
{% set y = "test.html'de tanımlı değil." %}
Evet, aşağı yukarı Jinja’daki temel kavramlardan bahsettiğimi düşünüyorum. Daha fazlası için Jinja2’nin apisini incelemenizi tavsiye ederim.
Herkese iyi günler.