Menjadi Developer Web dengan Python dan Flask Bagian II: Template

Bagus Aji Santoso 28 Desember 2017

Menjadi Developer Web dengan Python dan Flask Bagian II: Template

Artikel ini merupakan terjemahan Chapter 2: Templates dari seri The Flask Mega-Tutorial karya Miguel Grinberg yang akan dirilis secara gratis bab per bab sampai bulan Mei 2018. Dukung penulis aslinya dengan membeli buku/video tutorial lengkapnya di sini.

Daftar Isi:

  1. Menjadi Developer Web dengan Python dan Flask: Hello World
  2. Menjadi Developer Web dengan Python dan Flask: Template (Artikel ini)
  3. Menjadi Developer Web dengan Python dan Flask: Menampilkan dan Memproses Form

Setelah menyelesaikan Chapter 1 (versi Indonesia di sini), sekarang seharusnya kita sudah memiliki aplikasi web yang sangat sederhana dengan struktur file sebagai berikut:

microblog\
  venv\
  app\
    __init__.py
    routes.py
  microblog.py

Untuk menjalankan aplikasi ini kita mengatur FLASK_APP=microblog.py di terminal session lalu menjalankan perintah flask run. Perintah tadi memulai sebuah web server dengan aplikasi yang sudah kita buat. Aplikasi ini bisa dibuka melalui URL http://localhost:5000/ di address bar browser favorit.

Di bab ini kita akan melanjutkan aplikasi yang sama untuk membuat halaman web yang dengan struktur yang lebih kompleks dengan komponen dinamis yang banyak. Jika alur kerja aplikasi disini belum jelas, silahkan reviu lagi tutorial sebelumnya sebelum melanjutkan.

Daftar tautan Github untuk bab ini adalah: Browse, Zip, Diff.

Apa Itu Templates?

Penulis ingin membuat aplikasi microblogging ini memiliki sebauh heading yang menyambut user yang mengunjungi web yang sudah kita buat. Untuk saat ini penulis akan mengabaikan fakta bahwa aplikasi ini belum memiliki konsep user karena baru akan kita buat di beberapa bab ke depan. Untuk mengemulasi seorang user, penulis akan menggunakan dictionary Python sebagai berikut:

user = {'username': 'Miguel'}

Pembuatan user di atas (dikenal juga dengan istilah mocking) memungkinkan kita untuk berkonsentrasi menyelesaikan salah satu bagian aplikasi tanpa harus memikirkan bagian lain yang belum jadi. Dalam kasus ini kita ingin menyelesaikan tampilan web tanpa perlu memikirkan bagaimana sistem manajemen user-nya dan bisa terus bekerja.

View function di aplikasi kita baru mengembalikan sebauh string sederhana. Apa yang penulis inginkan saat ini adalah membuat string tersebut menjadi satu halaman HTML yang utuh, misalnya sebegai berikut:

from app import app

@app.route('/')
@app.route('/index')
def index():
    user = {'username': 'Miguel'}
    return '''
<html>
    <head>
        <title>Home Page - Microblog</title>
    </head>
    <body>
        <h1>Hello, ''' + user['username'] + '''!</h1>
    </body>
</html>'''

Jika belum familiar dengan HTML, penulis rekomendasikan untuk membaca HTML Markup di Wikipedia untuk pengenalan singkat.

Perbarui view function sesuai dengan kode di atas lalu coba buka browser untuk melihat bagaimana tampilan aplikasi yang baru saja kita perbarui.

Mock User

Penulis berharap pembaca setuju bahwa solusi di atas untuk mengirimkan HTML ke pengunjung merupakan solusi yang buruk. Bayangkan bagaimana ruwetnya view function ini saat kita nanti akan memiliki banyak artikel untuk blog milik user yang pastinya akan terus berubah-ubah secara konsisten. Aplikasi ini juga akan memiliki lebih banyak view function dengan semakin banyaknya URL yang akan kita buat, jadi bayangkan jika suatu hari nanti kita akan mengubah salah satu layout, maka kita harus memperbarui HTML di view function lainnya.

Jika kita bisa memisahkan logic dengan layout dari halaman web, tentu saja aplikasi kita akan menjadi lebih terorganisir bukan? Kita bahkan bisa menyewa seorang desainer web untuk membuat tampilan halaman web dan kita sendiri yang menulis application logic-nya dengan Python.

Templates membantu mencapai pemisahan antara presentation dan business logic. Di Flask, template ditulis sebagai file yang berbeda, disimpan di folder templates yang ada di dalam application package. Jadi setelah memastikan kita berada di direktori microblog, buat direktori untuk menyimpan templates:

(venv) $ mkdir app/templates

Berikut ini template kita yang pertama yang mana isinya mirip dengan kode html yang sebelumnya kita tulis secara manual di view function index(). Beri nama file ini index.html dan di simpan di app/templates/index.html:

<html>
    <head>
        <title>{{ title }} - Microblog</title>
    </head>
    <body>
        <h1>Hello, {{ user.username }}!</h1>
    </body>
</html>

Kode-kode di atas sangat standar, sama seperti halaman HTML lainnya. Bagian yang menarik dari halaman ini ialah adanya placeholder untuk konten dinamis yang disimpan di dalam {{ ... }}. Placeholder ini merepresentasikan bagian yang mungkin dapat berubah-ubah saat aplikasi berjalan.

Sekarang setelah membuat template, mari kita sederhanakan isi view function menjadi:

from flask import render_template
from app import app

@app.route('/')
@app.route('/index')
def index():
    user = {'username': 'Miguel'}
    return render_template('index.html', title='Home', user=user)

Operasi yang mengubah sebuah template menjadi halaman HTML utuh disebut dengan rendering. Untuk me-render template kita harus mengimpor sebauh fungsi Flask bernama render_template(). Fungsi ini meminta nama file template dan daftar variabel untuk diisi ke dalam placeholder yang ada di dalam template tersebut.

Funsi render_template() memanggil template engine bernama Jinja2 yang sudah ada bersama dengan framework Flask. Jinja2 mengganti blok {{ ... }} menjadi nilai yang diinginkan menggunakan argumen yang kita tulis di pemanggilanrender_template().

Percabangan

Kita sudah melihat bagaimana JInja2 mengagnti placeholder dengan nilai yang dikirimkan saat di render, namun fitur ini hanya salah satu dari banyak fitur lain yang didukung oleh Jinja2. Contohnya, template juga bisa membuat percabangan di dalam blok {% ... %}. Ubah isi index.html dengan menambah percabangan berikut:

<html>
    <head>
        {% if title %}
        <title>{{ title }} - Microblog</title>
        {% else %}
        <title>Welcome to Microblog!</title>
        {% endif %}
    </head>
    <body>
        <h1>Hello, {{ user.username }}!</h1>
    </body>
</html>

Sekarang template kita sedikit lebih cerdas. Jika view function lupa mengirimkan sebuah nilai untuk placeholder title, maka daripada menampilkan title kosong ia akan menampilkan title default. Pembaca bisa mengamati bagaimana cara kerjanya dengan menghapus argumen title di dalam pemanggilan render_template().

Perulangan

User yang baru login kemungkinan ingin melihat artikel-artikel terbaru di halaman depan, jadi apa yang kita akan lakukan sekarang adalah memperbarui aplikasi ini untuk dapat melakukannya.

Sekali lagi, kita akan memanfaatkan trik objek palsu untuk membuat user dan posts sebagai berikut:

from flask import render_template
from app import app

@app.route('/')
@app.route('/index')
def index():
    user = {'username': 'Miguel'}
    posts = [
        {
            'author': {'username': 'John'},
            'body': 'Beautiful day in Portland!'
        },
        {
            'author': {'username': 'Susan'},
            'body': 'The Avengers movie was so cool!'
        }
    ]
    return render_template('index.html', title='Home', user=user, posts=posts)

Untuk mewakili artikel-artikel dari user kita akan menggunakan sebuah list dimana setiap elemennya adalah dictionary yang memiliki author dan body. Saat nanti kita akan mengimplementasi user dan post sungguhan, kita akan usahakan menggunakan nama yang sama sehingga template yang bekerja dengan objek palsu ini masih bekerja dengan benar saat menggunakan objek yang sesungguhnya.

Di bagian template kita perlu menyelesaikan masalah baru, template tidak tahu ada berapa elemen yang harus ditampilkan. Hal ini karena yang tahu ada berapa artikel untuk ditampilkan hanyalah view function. Template tidak tahu ada berapa artikel yang ada sehingga ia harus dipersiapkan untuk me-render berapapun artikel yang dikirimkan oleh view function.

Untuk masalah ini, Jinja2 menawarkan opsi for:

<html>
    <head>
        {% if title %}
        <title>{{ title }} - Microblog</title>
        {% else %}
        <title>Welcome to Microblog</title>
        {% endif %}
    </head>
    <body>
        <h1>Hi, {{ user.username }}!</h1>
        {% for post in posts %}
        <div><p>{{ post.author.username }} says: <b>{{ post.body }}</b></p></div>
        {% endfor %}
    </body>
</html>

Tidak sulit kan? Coba lagi aplikasi ini dan pastikan untuk mengubah-ubah isi list posts untuk melihat bagaimana template memuat data yang baru sesuai dengan permintaan view function.

Mock Posts

Pewarisan Template

SEbagian besar aplikasi web dewasa ini memiliki sebuah navigation bar di bagian atas halaman untuk menampilkan tautan yang paling sering dibuka misalnya untuk mengakses profil, login, logout, dll. Kita dapat menambahkan sebuah navigation bar dengan mudah ke index.html, akan tetapi saat aplikasi kita semakin besar kita juga akan membutuhkan navigation bar yang sama di halaman lain. Tentu kita tidak ingin memiliki beberapa navigation bar yang berbeda-beda di halaman-halaman yang berbeda.

Jinja2 memiliki fitur pewarisan yang dapat menyelesaikan masalah ini. Opsi yang dapat kita lakukan adalah memisahkan bagian-bagian dari sebuah halaman yang akan dipakai oleh template lain ke sebuah base template.

Maka dari itu, sekarang bari kita tulis sebuah base template bernama base.html yang memiliki sebuah navigation bar sederhana dengan title yang sebelumnya sudah di buat. Tulis template ini di file app/templates/base.html:

<html>
    <head>
      {% if title %}
      <title>{{ title }} - Microblog</title>
      {% else %}
      <title>Welcome to Microblog</title>
      {% endif %}
    </head>
    <body>
        <div>Microblog: <a href="/index">Home</a></div>
        <hr>
        {% block content %}{% endblock %}
    </body>
</html>

Di template yang baru ini kita menambahkan block untuk mendefinisikan templat di mana template lain akan diisisipkan ke dalam template ini. Sebuah block dapat memiliki nama yang unik sehingga template lain dapat menyisipkannya di bagian block yang sesuai dengan kontennya.

Sekarang mari kita sederhanakan lagi isi index.html dengan membuatnya mewariskan template dari base.html:

{% extends "base.html" %}

{% block content %}
    <h1>Hi, {{ user.username }}!</h1>
    {% for post in posts %}
    <div><p>{{ post.author.username }} says: <b>{{ post.body }}</b></p></div>
    {% endfor %}
{% endblock %}

Karena struktur awalnya sudah ada di base.html, kita cukup menentukan bagian apa yang khusus ada di dalam index.html dan menghapus bagian yang sudah ada di base.html. Perintah extends akan membuat sebuah hubungan pewarisan antara dua template tadi. Dengan begitu Jinja2 akan tahu bahwa dia perlu me-render index.html di dalam base.html. Dua template memiliki perintah block dengan nama content sehingga Jinja2 dapat mengombinasikan kedua template ini dengan benar. SEkarang jika kita perlu membuat halaman baru, kita dapat membuatnya menjadi template yang mewariskan base.html. Pewarisan ini membuat halaman baru tersebut memiliki struktur yang sama dengan index.html tapi dengan data yang berbeda tampa melakukan duplikasi.

Template Inheritance