Python Pyramid

Views & Routes

Implementasi CRUD views dan konfigurasi routing untuk API Mahasiswa

Membuat Views untuk CRUD Mahasiswa

Views adalah fungsi yang menangani request dan mengembalikan response. Kita akan membuat views untuk operasi CRUD (Create, Read, Update, Delete) pada data Mahasiswa.

Buat File Views Mahasiswa

Buat file baru pyramid_mahasiswa/views/mahasiswa.py:

views/mahasiswa.py
import datetime
from pyramid.view import view_config
from pyramid.httpexceptions import (
    HTTPFound,
    HTTPNotFound,
    HTTPBadRequest,
)
from ..models import Mahasiswa


@view_config(route_name='mahasiswa_list', renderer='json')
def mahasiswa_list(request):
    """View untuk menampilkan daftar mahasiswa"""
    dbsession = request.dbsession
    mahasiswas = dbsession.query(Mahasiswa).all()
    return {'mahasiswas': [m.to_dict() for m in mahasiswas]}


@view_config(route_name='mahasiswa_detail', renderer='json')
def mahasiswa_detail(request):
    """View untuk melihat detail satu mahasiswa"""
    dbsession = request.dbsession
    mahasiswa_id = request.matchdict['id']
    mahasiswa = dbsession.query(Mahasiswa).filter_by(id=mahasiswa_id).first()

    if mahasiswa is None:
        return HTTPNotFound(json_body={'error': 'Mahasiswa tidak ditemukan'})

    return {'mahasiswa': mahasiswa.to_dict()}


@view_config(route_name='mahasiswa_add', request_method='POST', renderer='json')
def mahasiswa_add(request):
    """View untuk menambahkan mahasiswa baru"""
    try:
        # Ambil data dari request JSON
        json_data = request.json_body

        # Validasi data minimal
        required_fields = ['nim', 'nama', 'jurusan']
        for field in required_fields:
            if field not in json_data:
                return HTTPBadRequest(
                    json_body={'error': f'Field {field} wajib diisi'}
                )

        # Parse tanggal lahir jika ada
        tanggal_lahir = None
        if 'tanggal_lahir' in json_data and json_data['tanggal_lahir']:
            try:
                tanggal_lahir = datetime.datetime.fromisoformat(
                    json_data['tanggal_lahir']
                ).date()
            except ValueError:
                return HTTPBadRequest(
                    json_body={
                        'error': 'Format tanggal lahir tidak valid. Gunakan YYYY-MM-DD'
                    }
                )

        # Buat objek Mahasiswa baru
        mahasiswa = Mahasiswa(
            nim=json_data['nim'],
            nama=json_data['nama'],
            jurusan=json_data['jurusan'],
            tanggal_lahir=tanggal_lahir,
            alamat=json_data.get('alamat')
        )

        # Simpan ke database
        dbsession = request.dbsession
        dbsession.add(mahasiswa)
        dbsession.flush()  # Untuk mendapatkan ID yang baru dibuat

        return {'success': True, 'mahasiswa': mahasiswa.to_dict()}

    except Exception as e:
        return HTTPBadRequest(json_body={'error': str(e)})


@view_config(route_name='mahasiswa_update', request_method='PUT', renderer='json')
def mahasiswa_update(request):
    """View untuk mengupdate data mahasiswa"""
    dbsession = request.dbsession
    mahasiswa_id = request.matchdict['id']

    # Cari mahasiswa yang akan diupdate
    mahasiswa = dbsession.query(Mahasiswa).filter_by(id=mahasiswa_id).first()
    if mahasiswa is None:
        return HTTPNotFound(json_body={'error': 'Mahasiswa tidak ditemukan'})

    try:
        # Ambil data dari request JSON
        json_data = request.json_body

        # Update atribut yang ada di request
        if 'nim' in json_data:
            mahasiswa.nim = json_data['nim']
        if 'nama' in json_data:
            mahasiswa.nama = json_data['nama']
        if 'jurusan' in json_data:
            mahasiswa.jurusan = json_data['jurusan']
        if 'alamat' in json_data:
            mahasiswa.alamat = json_data['alamat']

        # Parse tanggal lahir jika ada
        if 'tanggal_lahir' in json_data:
            if json_data['tanggal_lahir']:
                try:
                    mahasiswa.tanggal_lahir = datetime.datetime.fromisoformat(
                        json_data['tanggal_lahir']
                    ).date()
                except ValueError:
                    return HTTPBadRequest(
                        json_body={
                            'error': 'Format tanggal lahir tidak valid. Gunakan YYYY-MM-DD'
                        }
                    )
            else:
                mahasiswa.tanggal_lahir = None

        return {'success': True, 'mahasiswa': mahasiswa.to_dict()}

    except Exception as e:
        return HTTPBadRequest(json_body={'error': str(e)})


@view_config(route_name='mahasiswa_delete', request_method='DELETE', renderer='json')
def mahasiswa_delete(request):
    """View untuk menghapus data mahasiswa"""
    dbsession = request.dbsession
    mahasiswa_id = request.matchdict['id']

    # Cari mahasiswa yang akan dihapus
    mahasiswa = dbsession.query(Mahasiswa).filter_by(id=mahasiswa_id).first()
    if mahasiswa is None:
        return HTTPNotFound(json_body={'error': 'Mahasiswa tidak ditemukan'})

    # Hapus dari database
    dbsession.delete(mahasiswa)

    return {
        'success': True,
        'message': f'Mahasiswa dengan id {mahasiswa_id} berhasil dihapus'
    }

Renderer JSON

Kita menggunakan renderer='json' pada decorator @view_config untuk mengonversi return value dari function view menjadi JSON response secara otomatis. Ini berguna untuk membuat API web yang mengembalikan data dalam format JSON.

Penjelasan View Functions

Mari kita pahami setiap view function yang telah dibuat:

1. mahasiswa_list

@view_config(route_name='mahasiswa_list', renderer='json')
def mahasiswa_list(request):
    dbsession = request.dbsession
    mahasiswas = dbsession.query(Mahasiswa).all()
    return {'mahasiswas': [m.to_dict() for m in mahasiswas]}

View ini:

  • Mengambil semua data mahasiswa dari database
  • Mengkonversi setiap objek Mahasiswa ke dictionary dengan to_dict()
  • Mengembalikan list dictionary dalam format JSON

2. mahasiswa_detail

@view_config(route_name='mahasiswa_detail', renderer='json')
def mahasiswa_detail(request):
    mahasiswa_id = request.matchdict['id']
    mahasiswa = dbsession.query(Mahasiswa).filter_by(id=mahasiswa_id).first()

View ini:

  • Mengambil ID dari URL parameter dengan request.matchdict['id']
  • Mencari mahasiswa dengan ID tersebut
  • Mengembalikan 404 jika tidak ditemukan

3. mahasiswa_add

@view_config(route_name='mahasiswa_add', request_method='POST', renderer='json')
def mahasiswa_add(request):
    json_data = request.json_body

View ini:

  • Menerima data JSON dari request body
  • Melakukan validasi field yang wajib diisi
  • Membuat objek Mahasiswa baru dan menyimpannya ke database

4. mahasiswa_update & mahasiswa_delete

View-view ini mengikuti pola yang sama dengan update dan delete operasi pada database.

Membuat Routes dan Update routes.py

Sekarang kita perlu mendefinisikan routes untuk endpoints CRUD Mahasiswa.

Update File routes.py

Edit file pyramid_mahasiswa/routes.py:

routes.py
def includeme(config):
    """Add routes to the config."""
    config.add_static_view('static', 'static', cache_max_age=3600)

    # Default route
    config.add_route('home', '/')

    # Mahasiswa routes
    config.add_route('mahasiswa_list', '/api/mahasiswa', request_method='GET')
    config.add_route('mahasiswa_detail', '/api/mahasiswa/{id}', request_method='GET')
    config.add_route('mahasiswa_add', '/api/mahasiswa', request_method='POST')
    config.add_route('mahasiswa_update', '/api/mahasiswa/{id}', request_method='PUT')
    config.add_route('mahasiswa_delete', '/api/mahasiswa/{id}', request_method='DELETE')

Request Method Parameter

Perhatikan penambahan parameter request_method pada setiap route. Ini sangat penting untuk membedakan endpoint yang memiliki URL sama tetapi method berbeda. Tanpa parameter ini, Pyramid mungkin akan selalu mengarahkan request ke satu view function saja, yang menyebabkan endpoint POST/PUT/DELETE tidak berfungsi.

RESTful API Pattern

API yang kita buat mengikuti pola RESTful dengan mapping sebagai berikut:

HTTP MethodURL PatternView FunctionDeskripsi
GET/api/mahasiswamahasiswa_listMendapatkan semua mahasiswa
GET/api/mahasiswa/{id}mahasiswa_detailMendapatkan detail satu mahasiswa
POST/api/mahasiswamahasiswa_addMenambahkan mahasiswa baru
PUT/api/mahasiswa/{id}mahasiswa_updateMengupdate data mahasiswa
DELETE/api/mahasiswa/{id}mahasiswa_deleteMenghapus data mahasiswa

RESTful API Best Practices

RESTful API menggunakan HTTP methods untuk menentukan jenis operasi:

  • GET: Mengambil data (read-only)
  • POST: Membuat data baru
  • PUT: Mengupdate data yang ada
  • DELETE: Menghapus data

Scan Views Module

Agar views yang telah kita buat dapat digunakan, kita perlu memastikan Pyramid melakukan scan pada module views.

Edit file pyramid_mahasiswa/__init__.py dan pastikan ada kode berikut:

__init__.py
def main(global_config, **settings):
    """ This function returns a Pyramid WSGI application.
    """
    (...)
    # Update Scan views module
    config.scan('.views')

    return config.make_wsgi_app()

Baris config.scan('.views') membuat Pyramid mencari semua decorator @view_config di dalam module views dan mendaftarkannya.

Config Scan

Pyramid menggunakan config scan untuk menemukan dan mendaftarkan views, routes, dan komponen lainnya secara otomatis. Tanpa config.scan(), decorator @view_config tidak akan berfungsi.

SQLAlchemy Query Patterns

Berikut beberapa pattern query SQLAlchemy yang sering digunakan:

# Mendapatkan semua data
mahasiswas = dbsession.query(Mahasiswa).all()

# Dengan ordering
mahasiswas = dbsession.query(Mahasiswa).order_by(Mahasiswa.nama).all()

# Dengan limit
mahasiswas = dbsession.query(Mahasiswa).limit(10).all()
# Filter by single field
mahasiswa = dbsession.query(Mahasiswa).filter_by(nim='12345').first()

# Filter dengan kondisi
mahasiswas = dbsession.query(Mahasiswa).filter(
    Mahasiswa.jurusan == 'Teknik Informatika'
).all()

# Multiple conditions
mahasiswas = dbsession.query(Mahasiswa).filter(
    Mahasiswa.jurusan == 'Teknik Informatika',
    Mahasiswa.nama.like('%budi%')
).all()
# Get first result or None
mahasiswa = dbsession.query(Mahasiswa).first()

# Get one result (raises if not found or multiple found)
mahasiswa = dbsession.query(Mahasiswa).filter_by(id=1).one()

# Get one or None
mahasiswa = dbsession.query(Mahasiswa).filter_by(id=1).one_or_none()
# Add new object
mahasiswa = Mahasiswa(nim='12345', nama='Budi')
dbsession.add(mahasiswa)

# Delete object
mahasiswa = dbsession.query(Mahasiswa).filter_by(id=1).first()
dbsession.delete(mahasiswa)

# Update object (directly modify attributes)
mahasiswa = dbsession.query(Mahasiswa).filter_by(id=1).first()
mahasiswa.nama = 'Budi Updated'
# No need to call update, SQLAlchemy tracks changes

Menjalankan Aplikasi

Setelah semua komponen diimplementasikan, jalankan aplikasi:

Menjalankan Server Development
# Pastikan virtual environment aktif
# Di root proyek
pserve development.ini --reload

Flag --reload akan menyebabkan server restart secara otomatis saat ada perubahan kode. Server akan berjalan pada port 6543 (http://localhost:6543).

Server Ready

Jika server berhasil dijalankan, Kalian akan melihat output:

Starting server in PID xxxxx.
Serving on http://localhost:6543

Langkah Selanjutnya

Setelah views dan routes selesai dibuat, kita akan melanjutkan ke pengujian API dan mengerjakan tugas praktikum pada bagian selanjutnya.