Masalah DX pada Proyek Django Tradisional

Pengembangan proyek Django dalam tim sering menghadapi tiga friksi developer experience (DX):

  • Tool sprawl dan inkonsistensi format: Penggunaan kombinasi Flake8, Black, dan isort memperlambat feedback loop lokal dan sering menghasilkan konflik konfigurasi antar-developer.
  • Kesalahan syntax DTL lolos ke runtime: Django Template Language (DTL) tidak diverifikasi saat interpreter Python membaca kode. Tag template yang tidak ditutup (misalnya {% if %} tanpa {% endif %}) baru terdeteksi saat view di-render di browser atau saat test runner dijalankan.
  • Bottleneck pada review PR: Code review terbuang untuk mengomentari spasi, urutan import, atau style template HTML, sementara pipeline CI berjalan lambat akibat menjalankan multiple linter lawas.

Solusinya adalah mengonsolidasikan tooling ke dua komponen modern: Ruff untuk Python (termasuk ruleset Django) dan djLint untuk template DTL, yang diikat langsung via pre-commit hook.

Konfigurasi pyproject.toml

Satukan konfigurasi Ruff dan djLint ke dalam pyproject.toml di root direktori proyek. Konfigurasi ini mengaktifkan linter, auto-formatter, sorting import, dan ruleset flake8-django (kode DJ).

[tool.ruff]
line-length = 88
target-version = "py312"
exclude = [
    "*/migrations/*",
    ".venv",
    "staticfiles",
]

[tool.ruff.lint]
select = [
    "E",      # pycodestyle errors
    "W",      # pycodestyle warnings
    "F",      # Pyflakes
    "I",      # isort (import sorting)
    "UP",     # pyupgrade
    "DJ",     # flake8-django
]
ignore = [
    "DJ001",  # Hindari warning Model.objects.all() bila tidak relevan
]

[tool.ruff.lint.isort]
known-first-party = ["my_project"]

[tool.djlint]
profile = "django"
indent = 2
max_line_length = 120
blank_line_after_tag = "extends,load"
ignore = "H006,H031"
format_css = true
format_js = true

Konfigurasi .pre-commit-config.yaml

Pre-commit memastikan eksekusi otomatis sebelum commit dibuat di git history. Buat file .pre-commit-config.yaml di root direktori:

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.6.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-added-large-files

  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.6.9
    hooks:
      # Linter & autofix (import sorting, ruleset DJ, syntax updates)
      - id: ruff
        args: [--fix]
      # Code formatter pengganti Black
      - id: ruff-format

  - repo: https://github.com/djlint/djLint
    rev: v1.35.2
    hooks:
      - id: djlint-reformat-django
      - id: djlint-django
Catatan: Jalankan perintah pre-commit install sekali di environment lokal Anda untuk mendaftarkan hook ke dalam .git/hooks/pre-commit.

Alur Eksekusi Lokal: Deteksi dan Auto-Fix

Ketika developer menulis kode dengan import berantakan pada views.py dan tag HTML yang rusak pada template DTL, pre-commit mengeksekusi pemeriksaan secara instan.

Contoh Kasus 1: views.py

# Sebelum commit (views.py)
from django.http import JsonResponse
import sys
from .models import Item
import os

def index(request):
    return JsonResponse({"status": "ok"})

Contoh Kasus 2: index.html

<!-- Sebelum commit (templates/index.html) -->
{% extends "base.html" %}
{% block content %}
<div class="container">
    <h1>Halo Dunia</h1>
<!-- Tag block sengaja tidak ditutup -->

Output Eksekusi Git Hook

$ git add views.py templates/index.html
$ git commit -m "feat: update view and template"

trim trailing whitespace.................................................Passed
fix end of files.........................................................Passed
check yaml...........................................(no files to check)Skipped
check for added large files..............................................Passed
ruff.....................................................................Failed
- hook id: ruff
- files were modified by this hook

Found 2 errors (2 fixed, 0 remaining).
Sorted imports in views.py.
Removed unused imports: sys, os.

ruff-format..............................................................Passed
djlint-reformat-django...................................................Passed
djlint-django............................................................Failed
- hook id: djlint-django
- exit code: 1

templates/index.html
  4:1  DJ011  Tag `block` on line 2 is never closed.  (django-tags)

Hasil eksekusi:

  1. Ruff otomatis merapikan import dan menghapus modul os serta sys yang tidak digunakan, lalu membatalkan commit agar perubahan dapat di-stage ulang.
  2. djLint memblokir commit karena menemukan tag {% block %} yang tidak memiliki penutup {% endblock %}, mencegah runtime error di production.

Integrasi pada Pipeline CI (GitHub Actions)

Developer dapat melewati hook lokal menggunakan flag git commit --no-verify. Untuk menjaga standar di repository bersama, jalankan verifikasi pre-commit yang sama di pipeline CI.

Tambahkan file .github/workflows/lint.yml:

name: Code Quality

on:
  pull_request:
    branches: [main, master]
  push:
    branches: [main, master]

jobs:
  pre-commit:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"
          cache: "pip"

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install pre-commit

      - name: Run pre-commit hooks
        run: pre-commit run --all-files --show-diff-on-failure

Trade-off dan Limitasi

  • djLint Regex Parser: djLint mengandalkan parser berbasis regex dan AST parsial untuk template, bukan parser Django C-level native. Pada struktur template yang sangat kompleks atau tag kustom non-standar, abaikan rule spesifik via konfigurasi ignore di pyproject.toml.
  • Cache pre-commit: Saat memperbarui versi hook di .pre-commit-config.yaml, jalankan pre-commit autoupdate dan bersihkan cache via pre-commit clean jika ditemukan inkonsistensi binary Ruff lokal.