Jinja Öğreniyorum

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.

5 Beğeni

Bu arada, daha önce duymamış olanlar için brython'dan bahsetmek isterim. brython, python'ın, js gibi kullanılmasını, python scriptlerini templateler içinde çalıştırmamızı sağlayan bir javascript kütüphanesidir.

Şöyle göstereyim.

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", world="world")
    
    
if __name__ == "__main__":
    app.run()

templates/main.html

<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/brython@3.12.0/brython.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/brython@3.12.0/brython_stdlib.js"></script>
<script type="text/python" src="{{ url_for('static', filename='py/main.py') }}"></script>
<script type="text/python">
    from browser import document
    x = "{{ world }}"
    document.attach(x)
    
    def f(x):
        print(x)

    f(3)
</script>

static/py/main.py

from browser import document

document.attach("hello")

Burada, brython kullanarak, hem static/py/main.py dosyasını template içine aktardık, hem server’dan brython’a world isimli bir değişken aktardık, hem de print(x) fonksiyonu ile browser’ın konsoluna 3 yazısını yazdırdık. Özetle Jinja’yı brython ile de kullanmak mümkün.

Neyse, meraklıları daha fazlasını araştıracaktır diye tahmin ediyorum.

Herkese iyi çalışmalar.

2 Beğeni

flask ı keşfedeli 1 ay falan oldu baya hoşuma gitti 1-2 işimi halletmemde yardımcı oldu bu yazınızda beni aydınlattı elinize sağlık hocam

1 Beğeni