Minggu Terakhir Akhir Sprint 1

Sprint 1 sudah selesai dengan lancar dan sekarang sudah masuk sprint 2. Di akhir sprint 1 tugas saya adalah menyelesaikan user story yang di-assign kepada saya yaitu DGB dapat melihat riwayat pengajuan CYD. Saya berkolaborasi dengan adit untuk menyelesaikan user story ini, saya lebih banyak bekerja di bagian back-end namun tetap membantu membuat front-end seperti pagination, dan lain-lain. Berikut adalah hasil dari user story DGB dapat melihat riwayat pengajuan CYD:

Halaman ini menampilkan daftar riwayat aplikasi yang disetujui maupun ditolak. Riwayat yang ditampilkan berupa detail dari CYD seperti nama, NIP, jabatan, dan lain-lain. Fitur pagination pada halaman ini belum terlihat karen jumlah entry pada masing-masing riwayat berhasil dan ditolak kurang dari 10.

Halaman riwayat aplikasi

Berikut adalah fitur lihat log, yaitu DGB dapat melihat catatan-catatan revisi dari aplikasi CYD.

Lihat log

Berikut adalah fitur download berkas. DGB dapat mengunduh berkas pengajuan maupun acceptence letter dari aplikasi CYD.

download berkas
Don’t Repeat Yourself

DRY merupakan salah satu prinsip development yang penting untuk digunakan. DRY atau yang kita kenal don’t repeat yourself mengharuskan kita untuk tidak menulis fungsionalitas yang sama. Membuat fungsionalitas yang sama memang tidak diperlukan dan membuang-buang waktu. Selain itu hal ini juga berguna untuk pengembangan selanjutnya, fungsionalitas yang sudah dibuat bisa digunakan lagi (reuseable) tanpa harus membuat testing lagi.

Hal tersebut lah yang saya lakukan di akhir sprint 1. User story DGB melihat pengajuan aplikasi dan Fakultas memantau pengajuan memiliki dua fungsionalitas yang sama, yaitu download berkas dan pagination. Oleh karena itu saya melakukan refactor, cukup dibuat satu fungsionalitas download dan pagination yang nantinya bisa digunakan dimana saja.

Saya membuat satu controller tersendiri untuk fungsionalitas download_file dan pagination serta satu testing untuk masing-masing controller tersebut. Berikut adalah potongan code dari download_file.py, pagination.py dan testingnya.

download_file.py

import os

from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpResponse

from ..models import Application, AcceptanceLetter, DocumentBundle


def download_berkas(cyd_id):
try:
app_id = Application.objects.get(cyd=cyd_id).id
file_path = DocumentBundle.objects.get(application=app_id).document.path
if os.path.exists(file_path):
with open(file_path, 'rb') as fh:
response = HttpResponse(fh.read(), content_type="application/force-download")
response['Content-Disposition'] = 'inline; filename=' + os.path.basename(file_path)
return response
else:
return HttpResponse('<html><h3>Berkas tidak ditemukan</h3></html>')
except ObjectDoesNotExist:
return HttpResponse('<html><h3>Berkas tidak ditemukan</h3></html>')


def download_acceptance_letter(cyd_id):
try:
app_id = Application.objects.get(cyd=cyd_id).id
file_path = AcceptanceLetter.objects.get(application=app_id).document.path
if os.path.exists(file_path):
with open(file_path, 'rb') as fh:
response = HttpResponse(fh.read(), content_type="application/force-download")
response['Content-Disposition'] = 'inline; filename=' + os.path.basename(file_path)
return response
else:
return HttpResponse('<html><h3>Acceptance letter belum tersedia</h3></html>')
except ObjectDoesNotExist:
return HttpResponse('<html><h3>Acceptance letter belum tersedia</h3></html>')

pagination.py

from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage


def pagination(request, name, input_list):
if name not in request.session:
request.session[name] = 1
page = request.GET.get(name, request.session[name])
per_page = 10
paginator = Paginator(input_list, per_page)
try:
cyd_list = paginator.page(page)
request.session[name] = page
except PageNotAnInteger:
cyd_list = paginator.page(1)
except EmptyPage:
cyd_list = paginator.page(paginator.num_pages)

return cyd_list

test_download_file.py

from django.test import TestCase
from django.test import Client
from mock import patch
from django.core.exceptions import ObjectDoesNotExist
from ..models import Level, Faculty, Status, Regulation, CalonYangDiusulkan, Application, DocumentBundle, \
AcceptanceLetter
from ..controllers import download_file as df
from mock import MagicMock


class DownloadFileTest(TestCase):
def create_objects(self):
regulation = Regulation.objects.create(name='Reguler 001')
teknik = Faculty.objects.create(name='Fakultas Teknik')
diterima = Status.objects.create(name='Disetujui', id=1)
lektor = Level.objects.create(name='Lektor')
lektor_kepala = Level.objects.create(name='Lektor Kepala')
self.ahmad = CalonYangDiusulkan.objects.create(nip='111113', name='Ahmad', birthplace='Malang',
current_level=lektor,
email='ahmad@abc.com', faculty=teknik, gender='L')
app_ahmad = Application.objects.create(cyd=self.ahmad, status=diterima, regulation=regulation,
destination_level=lektor_kepala)
self.berkas = DocumentBundle.objects.create(application=app_ahmad, document='berkas/2017/03/22/Artikel.zip')
self.acc = AcceptanceLetter.objects.create(application=app_ahmad,
document='acc_letter/2017/03/22/bajaj2015.pdf')

def setUp(self):
self.create_objects()
self.client = Client()
self.session = self.client.session

def test_download_success(self):
resp_berkas = df.download_berkas(self.ahmad.id)
resp_acc = df.download_acc(self.ahmad.id)
berkas = open(self.berkas.document.path, 'rb').read()
acc = open(self.acc.document.path, 'rb').read()
self.assertEqual(resp_berkas.content, berkas)
self.assertEqual(resp_acc.content, acc)

def test_download_false_path(self):
fake_doc = MagicMock()
fake_doc.document.path = 'randomstring-askdasdasdkk'
with
patch('os.path.exists', return_value=False):
resp_berkas = df.download_berkas(self.ahmad.id)
resp_acc = df.download_acc(self.ahmad.id)
self.assertEqual(resp_berkas.content, b'<html><h3>Berkas tidak ditemukan</h3></html>')
self.assertEqual(resp_acc.content, b'<html><h3>Acceptance letter belum tersedia</h3></html>')

def test_download_document_not_exist(self):
with patch('app.models.DocumentBundle.objects.get', side_effect=ObjectDoesNotExist), patch(
'app.models.AcceptanceLetter.objects.get', side_effect=ObjectDoesNotExist):
resp_berkas = df.download_berkas(self.ahmad.id)
resp_acc = df.download_acc(self.ahmad.id)
self.assertEqual(resp_berkas.content, b'<html><h3>Berkas tidak ditemukan</h3></html>')
self.assertEqual(resp_acc.content, b'<html><h3>Acceptance letter belum tersedia</h3></html>')

test_pagination.py

from unittest import TestCase
from django.test import Client
from django.urls import reverse


class PaginationTest(TestCase):
def setUp(self):
self.client = Client()

def test_pagination(self):
session = self.client.session
session['page3'] = [10]
session['page2'] = ['not number']
session['page1'] = [10000000000]
session.save()
url1 = reverse("fakultas_pantau_aktif")
url2 = reverse("fakultas_pantau_disetujui")
url3 = reverse("fakultas_pantau_dibatalkan")
self.client.get(url1)
self.client.get(url2)
self.client.get(url3)

Perhatikan bahwa untuk download berkas maupun acceptance letter hanya menggunakan satu controller saja sehingga cukup panggil fungsionalitas dari controller tersebut untuk melakukan download file. Berikut adalah contoh potongan code untuk download acceptance letter dan berkas:

from ..controllers import download_file as df


def download_berkas(request, cyd_id):
resp = df.download_berkas(cyd_id)
return resp


def download_acceptance_letter(request, cyd_id):
resp = df.download_acceptance_letter(cyd_id)
return resp

Code diatas merupakan download_views.py yang digunakan di dua tempat yaitu DGB riwayat pengajuan dan Fakultas memantu pengajuan.

Code naming and indentation

Penamaan dan indentasi sebuah code merupakan hal yang penting dan perlu diperhatikan. Penamaan yang bagus adalah penamaan yang cukup untuk merepresentasikan variable atau fungsionalitas tertentu yang dimana dapat dimengerti oleh programmer lain. Bayangkan saja jika kita memberi nama suatu variable model dengan nama “mdl” atau fungionalitas download_acceptance_letter dengan nama “dal”, programmer lain akan sulit mengerti. Indentasi yang baik memudahkan kita untuk membaca, memahami, memodifikasi suatu kodingan. Pada bahasa pemrograman java, code yang panjang dapat ditulis hanya dengan satu baris saja. Tentu saja suatu fungsionalitas yang membutuhkan beberapa baris akan sulit dipahami jika ditulis dengan satu baris saja.

Dari awal development saya sudah memperhatikan hal tersebut. Secara keseluruhan kami telah menggunakan penamaan variabel atau fungsionalitas yang dapat dipahami semua anggota kami dan juga programmer lain yang membacanya. Berikut adalah code yang saya tulis untuk fungsionalitas menampilkan aplikasi yang ditolak:

def get_rejected_applications():
try:
rejected_applications = []
rejected_status_changes = StatusChangeRecord.objects.filter(status=3).order_by('created_at')
for rsc in rejected_status_changes:
history_instance = {}
application = Application.objects.get(cyd=rsc.cyd.id)
history_instance['name'] = rsc.cyd.name
history_instance['nip'] = rsc.cyd.nip
history_instance['cyd_id'] = rsc.cyd.id
history_instance['faculty'] = rsc.cyd.faculty.name
history_instance['current_level'] = rsc.cyd.current_level.name
history_instance['destination_level'] = application.destination_level.name
app_histories = ApplicationHistory.objects.filter(cyd=rsc.cyd.id).order_by('revision_number')
logs = []
for app_history in app_histories:
notes = Record.objects.filter(application_history=app_history.id)
log = {'revision_number': app_history.revision_number, 'notes': notes}
logs += [log]
history_instance['logs'] = logs
rejected_applications += [history_instance]

return rejected_applications
except ObjectDoesNotExist:
return []

Perhatikan bahwa hampir semua variabel ditulis dengan nama lengkapnya. Jika variabel tersebut merupakan list, ditambahkan “s” dibagian belakang nama variabelnya.

Indentasi pada code kami sudah cukup bagus, karena kita menggunakan framework Django yang dimana bahasa pemrogramannya adalah python yang mengharuskan indentasi nya benar. Namun perlu diperhatikan bahwa penggunaan baris dan spasi yang berlebihan merupakan indentasi yang tidak baik. Hal ini saya atasi dengan menggunakan JetBrains PyCharm yang dimana PyCharm dapat mengatur indentasi dengan otomatis, kita cukup menekan shortcut ctrl+alt+L. Selain itu PyCharm juga menyediakan fitur yang powerful untuk development, seprti fitur import otomatis, runserver tanpa command line, dan lain-lain. Perhatikan bahwa selain menggunakan PyCharm untuk mengatur indentasi, saya sendiri harus tau bagaimana indentasi yang baik pada python karena terdapat beberapa kasus yang tidak bisa dihandle oleh PyCharm seperti block code didalam loop yang harus menjorok kedalam, jika kita lupa membuat beberapa baris code menjorok kedalam pada for loop maka pycharm hanya menganggap baris pertama saja yang merupakan body dari for loop tersebut.

Berikut adalah contoh code yang indentasinya sudah cukup baik.

Indentasi pada code menampilkan aplikasi yang disetujui
Git merge conflicts

Di akhir sprint kami sering melakukan merge dari branch user story ke branch develop ataupun sebaliknya. Dan sering kali terjadi conflict ketika proses merge tersebut. Merge conflict merupakan suatu hal yang wajar dan tidak boleh ditakuti :D, namun perlu untuk dihindari untuk menghindari kesalahan-kesalahan tertentu saat merge. Berikut adalah tips untuk menghindari merge conflict:

  • Ikuti aturan git flow yang telah disepakati
  • Pull terlebih dahulu dari branch develop sebelum melakukan merge atau sebelum membuat suatu fungsionalitas
  • Buat fungsionalitas/modul tidak terlalu besar dan jangan menunggu lama untuk merge ke develop jika sudah selesai implementasinya
  • Beritahukan teman jika mengubah code base
  • Perhatikan indentasi dan hal sepele lainnya. Kadang conflict terjadi karena hal sepele seperti penambahan line baru atau spasi saja.

Berikut adalah beberapa cara yang saya lakukan untuk menangani merge conflict:

  • Menggunakan source tree untuk resolve conflict, biasanya saya menggunakan fungsi resolve using theirs lalu menambahkan code saya yang diperlukan.
  • Komunikasikan dengan teman jika ada fungsionliatas yang sama diubah.
  • Pastikan semua berjalan dengan baik seteleh conflict selesai di resolve. Harus dicoba dijalankan (runserver) hingga testing yang sudah ok.

Itu saja yang dapat saya sampaikan. Mohon feedback dari teman-teman jika ada kesalahan. Semoga bermanfaat dan terimakasih. :)