Membangun Aplikasi Mini HRD dengan Django (3): Membuat Fitur Pengajuan Izin Ketidakhadiran

Ridwan Fajar 6 Juni 2016

Membangun Aplikasi Mini HRD dengan Django (3): Membuat Fitur Pengajuan Izin Ketidakhadiran

Di tutorial "Membuat Autentikasi Sederhana dan Mengenal Views", Anda telah mempelajari bagaimana membuat autentikasi sederhana dan mengolah views untuk menampilkan sebuah halaman HTML. Selain itu Anda juga telah berhasil menggunakan form untuk menampilkan data kehadiran seorang karyawan. Anda pun sudah dapat menampilkan data karyawan yang sedang login dengan bantuan django template. Di tutorial ini, kita akan lebih jauh mengenal django seperti:

  • mengenal form generator dengan ModelForm
  • mengenal pembuatan perintah khusus di halaman admin Django
Untuk melanjutkan tutorial berikutnya, kita akan membuat skenario dimana seorang karyawan dapat mengirimkan pengajuan izin tidak kerja kepada admin. Kemudian admin dapat mengkonfirmasi pengajuan izin tidak kerja mana yang diperbolehkan. Seketika cuti tersebut dikonfirmasi, maka rentang hari yang ada di pengajuan cuti akan tercatat ke tabel Kehadiran. Karyawan pun dapat melihat daftar pengajuan izin tidak kerja di halaman khusus.

Membuat model pengajuan izin kehadiran

Kita membutuhkan sebuah tabel yang akan mencatat pengajuan izin tidak hadir. Kita akan menamakan tabel tersebut dengan nama Izin. Field yang dibutuhkan antara lain siapa yang mengajukan izin, jenis kehadiran, waktu mulai izin, waktu berhenti izin, alasan kenapa izin, dan apakah disetujui atau tidak. Silahkan salin kode dibawah ini ke file kehadiran/models.py:
class Izin(models.Model):
    JENIS_KEHADIRAN_CHOICES = (
        ('izin', 'Izin'),
        ('cuti', 'Cuti')
    )
karyawan = models.ForeignKey(Karyawan)
jenis_kehadiran = models.CharField(max_length=20, choices=JENIS_KEHADIRAN_CHOICES)
waktu_mulai = models.DateField()
waktu_berhenti = models.DateField()
alasan = models.TextField()
disetujui = models.BooleanField(default=False)

def __unicode__(self):
    return self.karyawan.nama

Setelah yakin tidak ada yang salah dengan kode model Izin, maka saatnya kita melakukan migrasi untuk membuat tabel Izin di database:

$ python manage.py makemigrations
Migrations for 'kehadiran':
  0002_izin.py:
    - Create model Izin
$ python manage.py migrate
Operations to perform:
  Apply all migrations: kehadiran, sessions, admin, karyawan, contenttypes, auth
Running migrations:
  Rendering model states... DONE
  Applying kehadiran.0002_izin... OK
$

Pembuatan tabel Izin pun berhasil dilakukan setelah melakukan migrasi database, sekarang saatnya kita membuat sebuah class ModelAdmin untuk menampilkan halaman Izin di django admin. Silahkan salin kode dibawah ini di kehadiran/admin.py:

class IzinAdmin(admin.ModelAdmin):
    list_display = ['karyawan', 'jenis_kehadiran', 'waktu_mulai', 'waktu_berhenti', 'disetujui']
    list_filter = ('jenis_kehadiran', 'disetujui')
    search_fields = ['alasan']
    list_per_page = 25

admin.site.register(Izin, IzinAdmin)

kemudian nyalakan web server Django di dalam folder mini_hrd, dan sekarang kita dapat melihat tabel izin di halaman admin Django:

Selection_001 Selection_002

Membuat ModelForm untuk pengajuan izin kehadiran

Di tutorial ini Anda akan membuat sebuah form pengajuan izin tidak kerja. Namun kita akan mencoba bagaimana django dapat mempermudah kita membuat sebuah form yang sesuai dengan model yang kita definisikan plus dengan validasi yang tidak perlu kita tangani terlalu banyak. Yap, dengan ModelAdmin, Anda dapat menggunakan form yang sama persi di halaman admin Django untuk ditampilkan di halaman HTML yang Anda buat. Tidak hanya itu, validasi form pun telah dibuatkan untuk kita agar kita tidak repot dengan validasi form. Ada dua modul forms yang biasa digunakan, yaitu Form dan ModelForm. ModelForm lebih diperuntukkan bila form yang kita inginkan mirip sekali dengan model django. Silahkan salin kode dibawah ini ke file kehadiran/forms.py:
from django.forms import ModelForm
from django import forms
from kehadiran.models import Izin

class IzinForm(ModelForm): class Meta: model = Izin fields = ['jenis_kehadiran', 'waktu_mulai', 'waktu_berhenti', 'alasan'] labels = { 'jenis_kehadiran':"Jenis Izin", 'waktu_mulai':'Waktu Mulai Izin', 'waktu_berhenti':'Waktu Berhenti Izin', 'alasan':'Alasan Izin', } error_messages = { 'jenis_kehadiran': { 'required': 'Anda harus memilih jenis izin' }, 'waktu_mulai' : { 'required': "Anda harus menentukan tanggal izin dimulai" }, 'waktu_berhenti' : { 'required': "Anda harus menentukan tanggal izin berakhir" }, 'alasan':{ 'required': "Alasan harus diisi agar dapat disetujui oleh HRD" } } widgets = { 'alasan': forms.Textarea(attrs={ 'cols':50, 'rows': 10 }) }

Ada beberapahal yang perlu kita pahami bersama, ModelForm di-import dari django.forms, selain itu kita pun meng-import model Izin karena akan kita gunakan bersam ModelForm. Kita membuat sebuah kelas bernama IzinForm yang mewarisi class ModelForm. Kita harus menggunakan class Meta untuk mulai mendefinisikan ModelForm. Kita pilih model apakah yang akan kita targetkan untuk pembuatan form, dalam hal ini adalah model Izin. Kemudian kita pilih field apa sajakah yang akan ditampilkan sebagai form, karena tidak semua field harus ditampilkan menjadi form. Field "disetujui" dan "karyawan" tidak ditampilkan karena itu menjadi privasi admin.

Kemudian kita memperbaharui labels setiap field yang akan ditampilkan menjadi bahasa Indonesia. Secara otomatis field di ModelForm akan dilabel dengan nama field yang ada di model. Kemudian kita ubah juga pesan error yang akan dimunculkan ketika user salah memberikan masukan pada form. Kita memberikan pesan dalam bahasa Indonesia ketika field terkait salah pengisian. Dan khusus untuk field "alasan", kita harus mengubah jenis tampilan field-nya menjadi TextArea ketimbang TextInput. Ketika Anda mengubah widget suatu form, Anda dapat menyuntik atirbut form field sesuai dengan yang Anda inginkan. Anda pun dapat menyuntik nama class atau "data-*" atribut.

Setelah selesai mendefnisikan form, sekarang saatnya membuat function di kehadiran/views.py. Sekarang kita tambahkan peng-import-an IzinForm dan model Izin ke dalam views tersebut. Kemudian kita buat sebuah function dengan nama pengajuan_izin yang dibentengi dengan decorator login_required. Silahkan salin kode dibawah ini ke kehadiran/views.py (catatan: tanda garis titik - titik merupakan penunjuk bahwa ada kode program sebelumnya, jadi silahkan tambahkan function berikut dibawah kode terakhir dari file):

from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.conf import settings

from karyawan.models import Karyawan
from kehadiran.models import Kehadiran, Izin
from kehadiran.forms import IzinForm

.......................................

@login_required(login_url=settings.LOGIN_URL)
def pengajuan_izin(request):
    if request.method == 'POST':
        form_data = request.POST
        form = IzinForm(form_data)
        if form.is_valid():
            izin = Izin(
                    karyawan = Karyawan.objects.get(id=request.session['karyawan_id']),
                    jenis_kehadiran = request.POST['jenis_kehadiran'],
                    waktu_mulai = request.POST['waktu_mulai'],
                    waktu_berhenti = request.POST['waktu_berhenti'],
                    alasan = request.POST['alasan'],
                    disetujui = False,
                )
            izin.save()
            return redirect('/')
    else:
        form = IzinForm()

    return render(request, 'tambah_izin.html', {'form':form})

Di dalam function pengajuan_izin, terdapat proses pengecekan method form. Ketika yang datang adalah request berupa GET, maka Anda akan dibuatkan IzinForm kosong dan menampilkannya di template tambah_izin.html. Jika yang datang adalah request berupa POST, maka data POST akan dikumpulkan ke variabel form_data kemudian diperiksa oleh IzinForm untuk dicari validitasnya, jika nilai - nilai yang dimasukkan valid, maka proses selanjutnya akan dilakukan yaitu penyimpanan data pengajuan izin kerja. Kita membuat sebuah instance baru dari model Izin yang dilewatkan beberapa nilai dari form yang dikirimkan, kemudian kita simpan, dan melakukan redirect ke halaman root.

Form yang kita definisikan belum dapat dilihat, kita akan menampilkannya di kehadiran/templates/tambah_izin.html. Sebuah form django baik itu ModelForm ataupun Form, dapat di-render dalam berbagai cara. Kita dapat merendernya sebagai paragraf, unordered list, tabel, atapun manual rendering. Di dalam source code ini akan di-render dalam bentuk paragraf. Kita akan memanggil method as_p untuk melakukan hal tersebut. Silahkan salin kode dibawah ini ke kehadiran/templates/tambah_izin.html:

<!DOCTYPE html>
<htmL>
    <head>
        <title>Mini HRD</title>
    </head>
    <body>
        <h1>Form Pengajuan Izin Kerja</h1>

        <form action="/pengajuan_izin/" method="POST">
            {% csrf_token %}
            {{ form.as_p }}
            <input type="submit" value="Submit" />
        </form>

        <p><a href="/">Kembali ke Profil</a></p>
    </body>
</htmL>

Kemudian kita ubah link menu yang ada di template karyawan/profil.html, menjadi seperti berikut ini:

<!DOCTYPE html>
<htmL>
    <head>
        <title>Mini HRD</title>
    </head>
    <body>
        <h1>Silahkan Login</h1>
        <form>
            <h1>Profil Anda ({{ request.session.jenis_akun }})</h1>

            ........................................................

            <p>
                <a href="/logout/">Log out</a> | 
                <a href="/daftar_hadir/">Kehadiran</a> | 
                <a href="/pengajuan_izin/">Pengajuan Izin</a> | 
                <a href="/daftar_izin/">Daftar Izin</a> 
            </p>
        </form>
    </body>
</htmL>

Form pengajuan izin tidak kerja, akan ditampilkan ketika mengakses "/pengajuan_izin/", sedangkan "/daftar_izin/" akan ditangani oleh views yang akan kita kerjakan dibagian berikutnya. Sekarang mari kita tambahkan URL "/pengajuan_izin/" dan "/daftar_izin/" di urls.py:

kehadiran/urls.py:

from homepage import views as homepage_views
from karyawan import views as karyawan_views
from kehadiran import views as kehadiran_views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^$', karyawan_views.profil),
    url(r'^login/', homepage_views.login_view),
    url(r'^logout/', homepage_views.logout_view),
    url(r'^daftar_hadir/', kehadiran_views.daftar_hadir),
    url(r'^pengajuan_izin/', kehadiran_views.pengajuan_izin),
    url(r'^daftar_izin/', kehadiran_views.daftar_izin),
]

Sekarang mari kita lihat hasil sementara dari tutorial yang telah kita kerjakan sampai bagian ini:

Selection_004 Selection_005 Selection_003

Membuat konfirmasi pengajuan izin kehadiran

Karena persetujuan pengajuan izin kehadiran harus disetujui melalui halaman admin django, maka kita harus membuat sebuah bulk action sendiri untuk menyetujui pengajuan kehadiran. Selain itu kita pun harus membuat pembatalan pengajuan kehadiran juga. Ketika kita membuat menyetujui sebuah pengajuan izin kehadiran, kita akan membuat sederet tanggal yang dimulai dari waktu mulai izin kehadiran hingga waktu berakhir izin kehadiran. Setelah itu barulah kita mengubah field "disetujui" menjadi bernilai True. Sebaliknya bila membatalkan pengajuan izin kehadiran, field "disetujui" akan diubah menjadi bernilai False.

Silahkan ubah class ModelAdmin IzinAdmin di kehadiran/admin.py menjadi seperti berikut:

class IzinAdmin(admin.ModelAdmin):
    list_display = ['karyawan', 'jenis_kehadiran', 'waktu_mulai', 'waktu_berhenti', 'disetujui']
    list_filter = ('jenis_kehadiran', 'disetujui')
    search_fields = ['alasan']
    list_per_page = 25

    actions = ['setujui_izin', 'batalkan_izin']

    def setujui_izin(self, request, queryset):
       for izin in queryset:
            diff = izin.waktu_berhenti - izin.waktu_mulai
            base = izin.waktu_berhenti
            numdays = diff.days + 1
            date_list = [base - datetime.timedelta(days=x) for x in range(0, numdays)]

            for date in date_list:
                print date
                kehadiran = Kehadiran(
                            karyawan = izin.karyawan,
                            jenis_kehadiran = izin.jenis_kehadiran,
                            waktu = date
                        )

                kehadiran.save()

            izin.disetujui = True
            izin.save()

    setujui_izin.short_description = "Terima pengajuan izin yang dipilih"

    def batalkan_izin(self,request, queryset):
        queryset.update(disetujui=False)

    batalkan_izin.short_description = "Batalkan pengajuan izin yang dipilih"


admin.site.register(Izin, IzinAdmin)

Ada yang perlu diperhatikan dari kode diatas. Setiap actions yang akan didaftarkan merupakan nama function yang berisi custom admin action yang kita tulis. Kemudian Anda dapat menambahkan short description yang akan menjadi label ketika kita memilih perintah yang kita inginkan saat melihat daftar pengajuan izin kehadiran di halaman admin Django. Sekarang mari kita lihat hasil pekerjaan yang telah kita lakukan:

Selection_006 Selection_007 Selection_008 Selection_009 Selection_010

Menampilkan daftar pengajuan izin kehadiran

Karena kita akan menampilkan daftar pengajuan izin kehadiran di halaman karyawan, sekarang mari kita tambahkan function daftar_izin di kehadiran/views.py:
@login_required(login_url=settings.LOGIN_URL)
def daftar_izin(request):
    daftar_izin = Izin.objects.filter(karyawan__id=request.session['karyawan_id'])
    return render(request, 'daftar_izin.html', {'daftar_izin':daftar_izin})
Kemudian jangan lupa kita pun harus membuat file template yang akan menampilkan daftar pengajuan izin kerja kehadiran/templates/daftar_izin.html:
<!DOCTYPE html>
<htmL>
    <head>
        <title>Mini HRD</title>
    </head>
    <body>
        <h1>Daftar Izin</h1>
        <table border="1">
            <thead>
                <tr>
                    <td>No</td>
                    <td>Jenis Izin</td>
                    <td>Waktu Mulai</td>
                    <td>Waktu Berhenti</td>
                    <td>Alasan</td>
                    <td>Disetujui</td>
                </tr>
            </thead>
            <tbody>
                {% if daftar_izin %}
                    {% for izin in daftar_izin %}
                    <tr>
                        <td>{{ forloop.counter }}</td>
                        <td>{{ izin.jenis_kehadiran }}</td>
                        <td>{{ izin.waktu_mulai }}</td>
                        <td>{{ izin.waktu_berhenti }}</td>
                        <td>{{ izin.alasan }}</td>
                        <td>
                            {% if izin.disetujui %}
                            Sudah
                            {% else %}
                            Belum
                            {% endif %}
                        </td>
                    </tr>
                    {% endfor %}
                {% else %}
                <tr>
                    <td colspan="3">Tidak ada data pengajuan izin saat ini...</td>
                </tr>
                {% endif %}
            </tbody>
        </table>
        <p><a href="/">Kembali ke Profil</a></p>
    </body>
</htmL>
Sekarang mari kita lihat progress kita hingga bagian ini: Selection_011

Penutup

Di tutorial ini kita sudah mengenal bagaimana cara membuat form secara singkat dengan ModelForm, kita juga mengenal bagaimana cara membuat custom action di halaman admin django. Dan yang paling penting adalah bagaimana kita dapat mengenal beberapa query dengan menggunakan Django ORM. Di tutorial berikutnya, kita akan mempercantik halaman karyawan dengan menggunakan web template yang berbasis Twitter Bootstrap.

Selamat mencoba tutorial ini, pantengin terus Codepolitan untuk tutorial berikutnya :D.

(rfs/djangoproject)