Object-Oriented Programming dan Sistem Login Tkinter
Bagian 1: Refactoring ke Object-Oriented Programming
Sebelum menambahkan fitur baru, langkah pertama yang harus dilakukan developer profesional adalah merapikan kode yang sudah ada agar mudah dikembangkan. Kita akan mengubah aplikasi biodata dari script prosedural menjadi aplikasi berbasis kelas.
1.1 Mengapa Menggunakan Pendekatan OOP?
Aplikasi yang kita bangun di Praktikum sebelumnya menggunakan pendekatan prosedural dimana semua widget, variabel, dan fungsi didefinisikan secara global. Untuk aplikasi kecil hal ini tidak masalah, namun seiring aplikasi membesar, pendekatan ini akan menimbulkan masalah:
Masalah Pendekatan Prosedural:
- Variabel Global yang Berantakan: Semakin banyak widget dan data, semakin banyak variabel global yang kita miliki
- Kesulitan Manajemen: Bayangkan jika kita ingin memiliki dua form biodata dalam satu aplikasi
- Sulit Dikembangkan: Menambah fitur baru menjadi rumit karena logika dan tampilan tercampur
Keuntungan Pendekatan OOP:
- Enkapsulasi: Semua widget dan fungsi yang berhubungan dengan aplikasi akan dibungkus dalam satu "wadah" (kelas)
- Manajemen State: Variabel-variabel penting akan menjadi atribut dari instance kelas
- Reusability: Kelas aplikasi yang kita buat nantinya bisa diimpor dan digunakan kembali di proyek lain
Praktikum 1.1: Membuat Kerangka Kelas Aplikasi
Buka IDE dan buat file baru bernama aplikasi_biodata_oop.py dan ketik kode berikut:
import tkinter as tk
from tkinter import messagebox
# Membuat kelas utama aplikasi yang mewarisi dari tk.Tk
class AplikasiBiodata(tk.Tk):
# Metode __init__ adalah constructor yang akan dijalankan saat objek dibuat
def __init__(self):
# Memanggil constructor dari kelas induk (tk.Tk)
super().__init__()
# Mengkonfigurasi window utama
self.title("Aplikasi Biodata (Versi OOP)")
self.geometry("500x600")
self.resizable(True, True)
# (Di sini kita akan meletakkan semua kode GUI nantinya)
# Blok berikut hanya akan dieksekusi jika file ini dijalankan secara langsung
if __name__ == "__main__":
# Membuat instance dari kelas aplikasi kita
app = AplikasiBiodata()
# Menjalankan mainloop dari instance tersebut
app.mainloop()
Wajib: Sebelum melanjutkan ke praktikum berikutnya, setiap mahasiswa harus mengatur warna latar belakang jendela menggunakan
self.configure(bg="...")sesuai selera dan kreativitas masing-masing.
💡 Tips: Lihat daftar warna yang didukung Tkinter di: https://www.tcl.tk/man/tcl8.4/TkCmd/colors.htm
Praktikum 1.2: Migrasi Variabel Kontrol ke dalam Kelas
Tambahkan kode berikut di dalam metode __init__, setelah self.resizable(True, True):
# --- Variabel Kontrol Tkinter ---
# Variabel-variabel berikut sekarang menjadi atribut dari instance kelas
self.var_nama = tk.StringVar()
self.var_nim = tk.StringVar()
self.var_jurusan = tk.StringVar()
self.var_jk = tk.StringVar(value="Pria")
self.var_setuju = tk.IntVar()
# --- Frame Utama ---
# Frame utama juga menjadi atribut
self.main_frame = tk.Frame(master=self, padx=20, pady=20)
self.main_frame.pack(fill=tk.BOTH, expand=True)
self.main_frame.columnconfigure(1, weight=1)
# Aktifkan trace untuk validasi real-time
self.var_nama.trace_add("write", self.validate_form)
self.var_nim.trace_add("write", self.validate_form)
self.var_jurusan.trace_add("write", self.validate_form)
Tugas: Jalankan dan pastikan tidak ada error. Jendela masih kosong tetapi struktur sudah siap.
Praktikum 1.3: Migrasi Widget Interface - Bagian 1 (Header dan Input Dasar)
Lanjutkan di dalam __init__ dengan menambahkan widget header dan input dasar:
# --- Membuat dan Menempatkan Widget ---
# Judul
self.label_judul = tk.Label(
master=self.main_frame,
text="FORM BIODATA MAHASISWA",
font=("Arial", 16, "bold")
)
self.label_judul.grid(row=0, column=0, columnspan=2, pady=20)
# Frame khusus untuk input dengan border
self.frame_input = tk.Frame(
master=self.main_frame,
relief=tk.GROOVE,
borderwidth=2,
padx=10,
pady=10
)
# Input Nama
self.label_nama = tk.Label(
master=self.frame_input,
text="Nama Lengkap:",
font=("Arial", 12)
)
self.label_nama.grid(row=0, column=0, sticky="W", pady=2)
self.entry_nama = tk.Entry(
master=self.frame_input,
width=30,
font=("Arial", 12),
textvariable=self.var_nama
)
self.entry_nama.grid(row=0, column=1, pady=2)
# Input NIM
self.label_nim = tk.Label(
master=self.frame_input,
text="NIM:",
font=("Arial", 12)
)
self.label_nim.grid(row=1, column=0, sticky="W", pady=2)
self.entry_nim = tk.Entry(
master=self.frame_input,
width=30,
font=("Arial", 12),
textvariable=self.var_nim
)
self.entry_nim.grid(row=1, column=1, pady=2)
Tugas: Jalankan aplikasi. Anda akan melihat error karena metode validate_form belum dibuat, tapi widget sudah mulai muncul.
Praktikum 1.4: Migrasi Widget Interface - Bagian 2 (Input Lengkap)
Lanjutkan menambahkan input field yang lebih lengkap:
# Input Jurusan
self.label_jurusan = tk.Label(
master=self.frame_input,
text="Jurusan:",
font=("Arial", 12)
)
self.label_jurusan.grid(row=2, column=0, sticky="W", pady=2)
self.entry_jurusan = tk.Entry(
master=self.frame_input,
width=30,
font=("Arial", 12),
textvariable=self.var_jurusan
)
self.entry_jurusan.grid(row=2, column=1, pady=2)
# Input alamat dengan Text widget
self.label_alamat = tk.Label(
master=self.frame_input,
text="Alamat:",
font=("Arial", 12)
)
self.label_alamat.grid(row=3, column=0, sticky="NW", pady=2)
# Frame untuk Text dan Scrollbar
self.frame_alamat = tk.Frame(
master=self.frame_input,
relief=tk.SUNKEN,
borderwidth=1
)
# Scrollbar untuk alamat
self.scrollbar_alamat = tk.Scrollbar(master=self.frame_alamat)
self.scrollbar_alamat.pack(side=tk.RIGHT, fill=tk.Y)
# Text widget untuk alamat
self.text_alamat = tk.Text(
master=self.frame_alamat,
height=5,
width=28,
font=("Arial", 12)
)
self.text_alamat.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# Hubungkan scrollbar dengan text
self.scrollbar_alamat.config(command=self.text_alamat.yview)
self.text_alamat.config(yscrollcommand=self.scrollbar_alamat.set)
self.frame_alamat.grid(row=3, column=1, pady=2)
Tugas: Jalankan dan lihat Text widget dengan scrollbar yang muncul.
Praktikum 1.5: Migrasi Widget Interface - Bagian 3 (Radio Button dan Checkbox)
Tambahkan radio button untuk jenis kelamin dan checkbox:
# Jenis kelamin
self.label_jk = tk.Label(
master=self.frame_input,
text="Jenis Kelamin:",
font=("Arial", 12)
)
self.label_jk.grid(row=4, column=0, sticky="W", pady=2)
self.frame_jk = tk.Frame(master=self.frame_input)
self.frame_jk.grid(row=4, column=1, sticky="W")
self.radio_pria = tk.Radiobutton(
master=self.frame_jk,
text="Pria",
variable=self.var_jk,
value="Pria"
)
self.radio_pria.pack(side=tk.LEFT)
self.radio_wanita = tk.Radiobutton(
master=self.frame_jk,
text="Wanita",
variable=self.var_jk,
value="Wanita"
)
self.radio_wanita.pack(side=tk.LEFT)
# Checkbox persetujuan
self.check_setuju = tk.Checkbutton(
master=self.frame_input,
text="Saya menyetujui pengumpulan data ini.",
variable=self.var_setuju,
font=("Arial", 10),
command=self.validate_form
)
self.check_setuju.grid(row=5, column=0, columnspan=2, pady=10, sticky="W")
self.frame_input.grid(row=1, column=0, columnspan=2, sticky="EW")
Tugas: Jalankan dan test radio button serta checkbox.
Praktikum 1.6: Migrasi Widget Interface - Bagian 4 (Button dan Label Hasil)
Selesaikan dengan menambahkan tombol dan label hasil:
# Tombol submit
self.btn_submit = tk.Button(
master=self.main_frame,
text="Submit Biodata",
font=("Arial", 12, "bold"),
command=self.submit_data,
state=tk.DISABLED
)
self.btn_submit.grid(row=6, column=0, columnspan=2, pady=20, sticky="EW")
# Event bindings untuk hover dan keyboard shortcuts
self.btn_submit.bind("<Enter>", self.on_enter)
self.btn_submit.bind("<Leave>", self.on_leave)
# Keyboard shortcuts
self.entry_nama.bind("<Return>", self.submit_shortcut)
self.entry_nim.bind("<Return>", self.submit_shortcut)
self.entry_jurusan.bind("<Return>", self.submit_shortcut)
self.text_alamat.bind("<Return>", self.submit_shortcut)
# Label hasil
self.label_hasil = tk.Label(
master=self.main_frame,
text="",
font=("Arial", 12, "italic"),
justify=tk.LEFT
)
self.label_hasil.grid(row=7, column=0, columnspan=2, sticky="W", padx=10)
Tugas: Jalankan aplikasi. Sekarang akan ada banyak error karena method-method belum dibuat.
Praktikum 1.7: Migrasi Method Callback - Bagian 1 (Method Dasar)
Tambahkan method-method dasar di dalam kelas AplikasiBiodata, di luar __init__:
def submit_data(self):
# Cek checkbox
if self.var_setuju.get() == 0:
messagebox.showwarning("Peringatan", "Anda harus menyetujui pengumpulan data!")
return
# Ambil data dari form
nama = self.entry_nama.get()
nim = self.entry_nim.get()
jurusan = self.entry_jurusan.get()
alamat = self.text_alamat.get("1.0", tk.END).strip()
jenis_kelamin = self.var_jk.get()
# Cek field kosong
if not nama or not nim or not jurusan:
messagebox.showwarning("Input Kosong", "Semua field harus diisi!")
return
# Tampilkan hasil
hasil = f"Nama: {nama}\nNIM: {nim}\nJurusan: {jurusan}\nAlamat: {alamat}\nJenis Kelamin: {jenis_kelamin}"
messagebox.showinfo("Data Tersimpan", hasil)
# Tampilkan hasil di label
self.label_hasil.config(text=f"BIODATA TERSIMPAN:\n\n{hasil}")
def validate_form(self, *args):
nama_valid = self.var_nama.get().strip() != ""
nim_valid = self.var_nim.get().strip() != ""
jurusan_valid = self.var_jurusan.get().strip() != ""
setuju_valid = self.var_setuju.get() == 1
if nama_valid and nim_valid and jurusan_valid and setuju_valid:
self.btn_submit.config(state=tk.NORMAL)
else:
self.btn_submit.config(state=tk.DISABLED)
Tugas: Jalankan aplikasi dan test form validation. Tombol submit harus aktif hanya jika semua field terisi dan checkbox dicentang.
Praktikum 1.8: Migrasi Method Callback - Bagian 2 (Event Handlers)
Tambahkan method untuk event handlers:
def on_enter(self, event):
if self.btn_submit['state'] == tk.NORMAL:
self.btn_submit.config(bg="lightblue")
def on_leave(self, event):
self.btn_submit.config(bg="SystemButtonFace")
def submit_shortcut(self, event=None):
if self.btn_submit['state'] == tk.NORMAL:
self.submit_data()
Tugas: Test hover effect pada tombol dan keyboard shortcut dengan menekan Enter di field input.
Bagian 2: Membangun Sistem Multi-View dengan Login
Sekarang setelah kode kita rapi, kita bisa mulai menambahkan fitur login. Kita akan menggunakan pendekatan manajemen layar, dimana kita akan beralih antara frame login dan frame biodata di dalam satu window yang sama.
2.1 Memisahkan Tampilan (Views) menjadi Method Terpisah
Praktikum 2.1: Refactoring Tampilan Biodata
Buat method baru di dalam kelas AplikasiBiodata bernama _buat_tampilan_biodata(self) dan pindahkan semua kode pembuatan widget dari __init__ ke method ini. Ganti nama self.main_frame menjadi self.frame_biodata:
def _buat_tampilan_biodata(self):
# --- Variabel Kontrol Tkinter ---
self.var_nama = tk.StringVar()
self.var_nim = tk.StringVar()
self.var_jurusan = tk.StringVar()
self.var_jk = tk.StringVar(value="Pria")
self.var_setuju = tk.IntVar()
# Aktifkan trace untuk validasi real-time
self.var_nama.trace_add("write", self.validate_form)
self.var_nim.trace_add("write", self.validate_form)
self.var_jurusan.trace_add("write", self.validate_form)
# --- Frame Biodata ---
self.frame_biodata = tk.Frame(master=self, padx=20, pady=20)
self.frame_biodata.columnconfigure(1, weight=1)
# Judul
self.label_judul = tk.Label(
master=self.frame_biodata,
text="FORM BIODATA MAHASISWA",
font=("Arial", 16, "bold")
)
self.label_judul.grid(row=0, column=0, columnspan=2, pady=20)
Tugas: Pindahkan semua kode widget dari __init__ ke method ini, ganti self.main_frame dengan self.frame_biodata.
Praktikum 2.2: Modifikasi Constructor untuk Multi-View
Modifikasi __init__ untuk mendukung sistem multi-view:
def __init__(self):
super().__init__()
self.title("Aplikasi Biodata Mahasiswa")
self.geometry("600x700")
self.resizable(True, True)
# Atribut untuk manajemen frame
self.frame_aktif = None
# Buat tampilan
self._buat_tampilan_login()
self._buat_tampilan_biodata()
# Tampilkan frame login di awal
self._pindah_ke(self.frame_login)
Tugas: Jalankan dan pastikan tidak ada error (meskipun akan error karena method login belum dibuat).
Praktikum 2.3: Membuat Tampilan Form Login - Bagian 1 (Layout Dasar)
Tambahkan method baru untuk membuat tampilan login:
def _buat_tampilan_login(self):
self.frame_login = tk.Frame(master=self, padx=20, pady=100)
# Konfigurasi grid untuk frame login agar terpusat
self.frame_login.grid_columnconfigure(0, weight=1)
self.frame_login.grid_columnconfigure(1, weight=1)
# Judul Login
tk.Label(
self.frame_login,
text="HALAMAN LOGIN",
font=("Arial", 16, "bold")
).grid(row=0, column=0, columnspan=2, pady=20)
# Input Username
tk.Label(
self.frame_login,
text="Username:",
font=("Arial", 12)
).grid(row=1, column=0, sticky="W", pady=5)
self.entry_username = tk.Entry(self.frame_login, font=("Arial", 12))
self.entry_username.grid(row=1, column=1, pady=5, sticky="EW")
Tugas: Jalankan dan lihat tampilan login yang mulai terbentuk.
Praktikum 2.4: Membuat Tampilan Form Login - Bagian 2 (Input dan Button)
Lanjutkan menambahkan input password dan tombol login:
# Input Password
tk.Label(
self.frame_login,
text="Password:",
font=("Arial", 12)
).grid(row=2, column=0, sticky="W", pady=5)
self.entry_password = tk.Entry(
self.frame_login,
font=("Arial", 12),
show="*"
)
self.entry_password.grid(row=2, column=1, pady=5, sticky="EW")
# Tombol Login
self.btn_login = tk.Button(
self.frame_login,
text="Login",
font=("Arial", 12, "bold"),
command=self._coba_login
)
self.btn_login.grid(row=3, column=0, columnspan=2, pady=20, sticky="EW")
# Keyboard shortcuts untuk login
self.entry_username.bind("<Return>", lambda e: self.entry_password.focus_set())
self.entry_password.bind("<Return>", lambda e: self._coba_login())
# Info untuk user
info_label = tk.Label(
self.frame_login,
text="Info: Username yang tersedia:\nadmin (password: 123)\nuser1 (password: password1)\nmahasiswa (password: 123456)",
font=("Arial", 9),
fg="gray",
justify=tk.LEFT
)
info_label.grid(row=4, column=0, columnspan=2, pady=10)
Tugas: Jalankan dan lihat form login yang lengkap.
Praktikum 2.5: Membuat System Navigation
Tambahkan method untuk navigasi antar tampilan:
def _pindah_ke(self, frame_tujuan):
"""Method untuk berpindah antar tampilan"""
if self.frame_aktif is not None:
self.frame_aktif.pack_forget()
self.frame_aktif = frame_tujuan
self.frame_aktif.pack(fill=tk.BOTH, expand=True)
# Auto-focus berdasarkan frame yang ditampilkan
if frame_tujuan == self.frame_login:
self.after(100, lambda: self.entry_username.focus_set())
elif frame_tujuan == self.frame_biodata:
self.after(100, lambda: self.entry_nama.focus_set())
Tugas: Jalankan aplikasi. Seharusnya sekarang hanya tampilan login yang muncul. Jika error hal itu terjadi karena kita belum menambahkan method _coba_login yang akan kita tambahkan nantinya.
Bagian 3: Implementasi Logic Login dan Logout
3.1 Implementasi Logic Login
Praktikum 3.1: Membuat Database User Sederhana
Tambahkan database user di dalam __init__:
def __init__(self):
super().__init__()
self.title("Aplikasi Biodata Mahasiswa")
self.geometry("600x700")
self.resizable(True, True)
# Database user sederhana (dalam aplikasi nyata, ini akan di database)
self.users_db = {
"admin": "123",
"user1": "password1",
"mahasiswa": "123456"
}
# Status login
self.current_user = None
# Atribut untuk manajemen frame
self.frame_aktif = None
# Buat tampilan
self._buat_tampilan_login()
self._buat_tampilan_biodata()
# Tampilkan frame login di awal
self._pindah_ke(self.frame_login)
Tugas: Buat user baru menggunakan format username nama(NIM). User ini wajib digunakan selama testing aplikasi.
Praktikum 3.2: Implementasi Validasi Kredensial - Bagian 1 (Validasi Input)
Tambahkan method untuk memproses login:
def _coba_login(self):
"""Method untuk memproses attempt login"""
username = self.entry_username.get().strip()
password = self.entry_password.get()
# Validasi input kosong
if not username or not password:
messagebox.showwarning("Login Gagal", "Username dan Password tidak boleh kosong.")
self.entry_username.focus_set()
return
# Validasi panjang minimum
if len(username) < 3:
messagebox.showwarning("Login Gagal", "Username minimal 3 karakter.")
self.entry_username.focus_set()
return
Tugas: Test validasi dengan input kosong dan username pendek.
Praktikum 3.3: Implementasi Validasi Kredensial - Bagian 2 (Cek Database)
Lanjutkan method _coba_login dengan pengecekan database:
# Cek kredensial di database
if username in self.users_db and self.users_db[username] == password:
self.current_user = username
messagebox.showinfo("Login Berhasil", f"Selamat Datang, {username}!")
self._reset_form_biodata()
self._update_title_with_user()
self._pindah_ke(self.frame_biodata)
# Bersihkan field login setelah berhasil
self.entry_username.delete(0, tk.END)
self.entry_password.delete(0, tk.END)
else:
messagebox.showerror("Login Gagal", "Username atau Password salah.")
# Bersihkan password dan focus ke username
self.entry_password.delete(0, tk.END)
self.entry_username.focus_set()
Tugas: Test login dengan kredensial yang benar dan salah.
Praktikum 3.4: Method Helper untuk Login
Tambahkan method helper yang dibutuhkan:
def _reset_form_biodata(self):
"""Reset semua field di form biodata"""
self.var_nama.set("")
self.var_nim.set("")
self.var_jurusan.set("")
self.text_alamat.delete("1.0", tk.END)
self.var_jk.set("Pria")
self.var_setuju.set(0)
self.label_hasil.config(text="")
def _update_title_with_user(self):
"""Update judul window dengan nama user yang login"""
if self.current_user:
self.title(f"Aplikasi Biodata Mahasiswa - User: {self.current_user}")
else:
self.title("Aplikasi Biodata Mahasiswa")
Tugas: Test login lengkap dan perhatikan perubahan title window.
3.2 Implementasi Logic Logout
Praktikum 3.5: Menambahkan Fitur Logout
Tambahkan method logout:
def _logout(self):
"""Method untuk logout dan kembali ke halaman login"""
if messagebox.askyesno("Logout", f"Apakah {self.current_user} yakin ingin logout?"):
# Reset status user
self.current_user = None
# Hapus menu
self._hapus_menu()
# Update title
self._update_title_with_user()
# Bersihkan field login
self.entry_username.delete(0, tk.END)
self.entry_password.delete(0, tk.END)
# Reset form biodata
self._reset_form_biodata()
# Kembali ke halaman login
self._pindah_ke(self.frame_login)
# Focus ke username field
self.entry_username.focus_set()
Tugas: Method ini sudah siap, tapi belum ada cara untuk memanggilnya.
Praktikum 3.6: Membuat Menu Bar
Tambahkan method untuk membuat menu dan panggil di akhir _buat_tampilan_biodata():
def _buat_menu(self):
"""Membuat menu bar untuk aplikasi"""
menu_bar = tk.Menu(master=self)
self.config(menu=menu_bar)
file_menu = tk.Menu(master=menu_bar, tearoff=0)
file_menu.add_command(label="Logout", command=self._logout)
file_menu.add_separator()
file_menu.add_command(label="Keluar", command=self.destroy)
menu_bar.add_cascade(label="File", menu=file_menu)
Tambahkan di akhir method _buat_tampilan_biodata():
Tambahkan juga setelah method _buat_menu(), method _hapus_menu. Method ini berfungsi ketika berada di halaman login, menu bar tidak ada:
def _hapus_menu(self):
"""Menghapus menu bar dari window."""
empty_menu = tk.Menu(self)
self.config(menu=empty_menu)
Tugas: Test fitur logout melalui menu File.
Bagian 4: Peningkatan User Experience dan Keamanan
4.1 Menambahkan Validasi
Praktikum 4.1: Implementasi Validasi Form Biodata - Bagian 1 (Validasi Dasar)
Perbarui method submit_data dengan validasi yang lebih lengkap:
def submit_data(self):
"""Submit data biodata dengan validasi lengkap"""
try:
# Cek checkbox
if self.var_setuju.get() == 0:
messagebox.showwarning("Peringatan", "Anda harus menyetujui pengumpulan data!")
return
# Ambil data dari form
nama = self.entry_nama.get().strip()
nim = self.entry_nim.get().strip()
jurusan = self.entry_jurusan.get().strip()
alamat = self.text_alamat.get("1.0", tk.END).strip()
jenis_kelamin = self.var_jk.get()
# Validasi field kosong
if not nama or not nim or not jurusan:
messagebox.showwarning("Input Kosong", "Nama, NIM, dan Jurusan harus diisi!")
return
Tugas: Test validasi dasar ini dulu.
Praktikum 4.2: Implementasi Validasi Form Biodata - Bagian 2 (Validasi Format)
Lanjutkan method submit_data dengan validasi format:
# Validasi format NIM (harus angka dan minimal 8 digit)
if not nim.isdigit() or len(nim) < 8:
messagebox.showwarning("Format NIM Salah", "NIM harus berupa angka minimal 8 digit!")
self.entry_nim.focus_set()
return
# Validasi nama (tidak boleh hanya angka)
if nama.isdigit():
messagebox.showwarning("Format Nama Salah", "Nama tidak boleh hanya berupa angka!")
self.entry_nama.focus_set()
return
# Tampilkan hasil
hasil = f"Nama: {nama}\nNIM: {nim}\nJurusan: {jurusan}\nAlamat: {alamat}\nJenis Kelamin: {jenis_kelamin}"
messagebox.showinfo("Data Tersimpan", hasil)
# Tampilkan hasil di label dengan info user
hasil_lengkap = f"BIODATA TERSIMPAN:\nDiinput oleh: {self.current_user}\n\n{hasil}"
self.label_hasil.config(text=hasil_lengkap)
except Exception as e:
messagebox.showerror("Error", f"Terjadi kesalahan saat memproses data:\n{str(e)}")
Tugas: Test validasi dengan berbagai input yang salah (NIM kurang dari 8 digit, nama berupa angka, dll).
4.2 Menambahkan Fitur Simpan File
Praktikum 4.3: Implementasi Simpan ke File
Tambahkan import datetime di bagian atas file:
Tambahkan method untuk menyimpan hasil ke file:
def simpan_hasil(self):
"""Simpan hasil biodata ke file dengan error handling"""
try:
hasil_tersimpan = self.label_hasil.cget("text")
if not hasil_tersimpan or "BIODATA TERSIMPAN" not in hasil_tersimpan:
messagebox.showwarning("Peringatan", "Tidak ada data untuk disimpan. Mohon submit terlebih dahulu.")
return
# Buat nama file dengan timestamp
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"biodata_{self.current_user}_{timestamp}.txt"
with open(filename, "w", encoding="utf-8") as file:
file.write(f"Data disimpan oleh: {self.current_user}\n")
file.write(f"Waktu penyimpanan: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
file.write("-" * 50 + "\n")
file.write(hasil_tersimpan)
messagebox.showinfo("Info", f"Data berhasil disimpan ke file '{filename}'.")
except PermissionError:
messagebox.showerror("Error", "Tidak memiliki izin untuk menyimpan file di lokasi ini.")
except Exception as e:
messagebox.showerror("Error", f"Terjadi kesalahan saat menyimpan file:\n{str(e)}")
Tugas: Test method ini dengan memanggil langsung dulu.
Praktikum 4.4: Menambahkan Menu Simpan
Perbarui method _buat_menu() untuk menambahkan opsi simpan:
def _buat_menu(self):
"""Membuat menu bar untuk aplikasi"""
menu_bar = tk.Menu(master=self)
self.config(menu=menu_bar)
file_menu = tk.Menu(master=menu_bar, tearoff=0)
file_menu.add_command(label="Simpan Hasil", command=self.simpan_hasil)
file_menu.add_separator()
file_menu.add_command(label="Logout", command=self._logout)
file_menu.add_separator()
file_menu.add_command(label="Keluar", command=self.destroy)
menu_bar.add_cascade(label="File", menu=file_menu)
Tugas: Test fitur simpan file setelah submit biodata.
Bagian 5: Testing dan Debugging
5.1 Menambahkan Logging System
Praktikum 5.1: Setup Logging
Tambahkan import logging di bagian atas file:
import tkinter as tk
from tkinter import messagebox
import datetime
import logging
# Setup logging
logging.basicConfig(
filename='aplikasi_biodata.log',
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
Tugas: Jalankan aplikasi dan cek apakah file log terbuat.
Praktikum 5.2: Menambahkan Logging ke Login Process
Perbarui method _coba_login dengan logging:
def _coba_login(self):
"""Method untuk memproses attempt login dengan logging"""
username = self.entry_username.get().strip()
password = self.entry_password.get()
# Log attempt login
logging.info(f"Login attempt for username: {username}")
# Validasi input kosong
if not username or not password:
logging.warning(f"Empty credentials attempt for username: {username}")
messagebox.showwarning("Login Gagal", "Username dan Password tidak boleh kosong.")
self.entry_username.focus_set()
return
# Validasi panjang minimum
if len(username) < 3:
logging.warning(f"Username too short: {username}")
messagebox.showwarning("Login Gagal", "Username minimal 3 karakter.")
self.entry_username.focus_set()
return
# Cek kredensial di database
if username in self.users_db and self.users_db[username] == password:
self.current_user = username
logging.info(f"Successful login for user: {username}")
messagebox.showinfo("Login Berhasil", f"Selamat Datang, {username}!")
self._reset_form_biodata()
self._update_title_with_user()
self._pindah_ke(self.frame_biodata)
self.entry_username.delete(0, tk.END)
self.entry_password.delete(0, tk.END)
else:
logging.warning(f"Failed login attempt for username: {username}")
messagebox.showerror("Login Gagal", "Username atau Password salah.")
self.entry_password.delete(0, tk.END)
self.entry_username.focus_set()
Tugas: Test aplikasi dan periksa file log untuk melihat aktivitas yang tercatat.
Praktikum 5.3: Menambahkan Logging ke Aktivitas Lain
Tambahkan logging pada method _logout dan submit_data:
def _logout(self):
"""Method untuk logout dengan logging"""
if messagebox.askyesno("Logout", f"Apakah {self.current_user} yakin ingin logout?"):
logging.info(f"User logout: {self.current_user}")
# Reset status user
self.current_user = None
# Update title
self._update_title_with_user()
# Bersihkan field login
self.entry_username.delete(0, tk.END)
self.entry_password.delete(0, tk.END)
# Reset form biodata
self._reset_form_biodata()
# Kembali ke halaman login
self._pindah_ke(self.frame_login)
# Focus ke username field
self.entry_username.focus_set()
Tambahkan di method submit_data setelah hasil ditampilkan:
# Log successful data submission
logging.info(f"Data submitted by user: {self.current_user} - NIM: {nim}")
Dan di bagian except:
except Exception as e:
logging.error(f"Error in submit_data by {self.current_user}: {str(e)}")
messagebox.showerror("Error", f"Terjadi kesalahan saat memproses data:\n{str(e)}")
Tugas: Test semua fitur dan periksa log file untuk melihat tracking yang lengkap.
Praktikum 5.4: Menambahkan Logging Startup
Tambahkan di akhir method __init__:
Tambahkan method untuk keluar aplikasi dengan logging:
def keluar_aplikasi(self):
"""Keluar dari aplikasi dengan konfirmasi"""
if messagebox.askokcancel("Keluar", "Apakah Anda yakin ingin keluar dari aplikasi?"):
logging.info(f"Application closed by user: {self.current_user}")
self.destroy()
Perbarui menu untuk menggunakan method ini:
Tugas: Test aplikasi lengkap dan periksa semua log yang tercatat.
Tugas dan Latihan
Tugas 1: Implementasi Lengkap
Implementasikan semua kode yang telah dijelaskan dalam modul ini dan pastikan aplikasi berjalan dengan baik.
Tugas 2: Fitur Tambahan
Tambahkan fitur-fitur berikut:
- Remember Me: Checkbox untuk mengingat username terakhir
- Show/Hide Password: Tombol untuk menampilkan/menyembunyikan password
- Reset Form: Tombol untuk reset semua field biodata
Tugas 3: Validasi Lanjutan
Implementasikan validasi tambahan:
- Email: Tambahkan field email dengan validasi format
- Telepon: Tambahkan field telepon dengan validasi format Indonesia
- Tanggal Lahir: Tambahkan field tanggal lahir