import csv import datetime import random from django.shortcuts import render, redirect, get_object_or_404 from django.http import HttpResponse, JsonResponse from django.contrib import messages from django.db import IntegrityError from openpyxl import Workbook from .models import ( ProgramStudi, Dosen, TahunAkademik, Hari, MataKuliah, Ruangan, Kelas, Jam, DosenPengampu, Jadwal, DaftarHadir ) def dashboard(request): context = { "count_prodi": ProgramStudi.objects.count(), "count_dosen": Dosen.objects.count(), "count_matkul": MataKuliah.objects.count(), "count_ruangan": Ruangan.objects.count(), "count_kelas": Kelas.objects.count(), "count_jadwal": Jadwal.objects.count(), "count_daftar_hadir": DaftarHadir.objects.count(), } return render(request, "core/dashboard.html", context) # --- AJAX HELPERS --- def get_kelas_by_prodi(request): prodi_id = request.GET.get('prodi_id') kelas = Kelas.objects.filter(prodi_id=prodi_id).values('id', 'nama') return JsonResponse(list(kelas), safe=False) def get_matkul_by_prodi(request): prodi_id = request.GET.get('prodi_id') matkul = MataKuliah.objects.filter(prodi_id=prodi_id).values('id', 'kode', 'nama') return JsonResponse(list(matkul), safe=False) # --- PRODI --- def prodi_list(request): prodi_all = ProgramStudi.objects.all() if request.method == "POST": if "delete_massal" in request.POST: ids = request.POST.getlist("selected_ids") ProgramStudi.objects.filter(id__in=ids).delete() messages.success(request, f"Berhasil menghapus {len(ids)} Program Studi.") else: pk = request.POST.get("pk") nama = request.POST.get("nama") if pk: obj = get_object_or_404(ProgramStudi, pk=pk) obj.nama = nama obj.save() messages.success(request, "Program Studi berhasil diupdate.") else: ProgramStudi.objects.create(nama=nama) messages.success(request, "Program Studi berhasil ditambahkan.") return redirect("prodi_list") return render(request, "core/prodi_list.html", {"prodi_all": prodi_all}) def prodi_delete(request, pk): get_object_or_404(ProgramStudi, pk=pk).delete() messages.success(request, "Program Studi berhasil dihapus.") return redirect("prodi_list") # --- DOSEN --- def dosen_list(request): dosen_all = Dosen.objects.all() if request.method == "POST": if "delete_massal" in request.POST: ids = request.POST.getlist("selected_ids") Dosen.objects.filter(id__in=ids).delete() messages.success(request, f"Berhasil menghapus {len(ids)} Dosen.") else: pk = request.POST.get("pk") nidn = request.POST.get("nidn") nama = request.POST.get("nama") if pk: obj = get_object_or_404(Dosen, pk=pk) obj.nidn = nidn obj.nama = nama obj.save() messages.success(request, "Dosen berhasil diupdate.") else: Dosen.objects.create(nidn=nidn, nama=nama) messages.success(request, "Dosen berhasil ditambahkan.") return redirect("dosen_list") return render(request, "core/dosen_list.html", {"dosen_all": dosen_all}) def dosen_delete(request, pk): get_object_or_404(Dosen, pk=pk).delete() messages.success(request, "Dosen berhasil dihapus.") return redirect("dosen_list") def dosen_template(request): response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="template_dosen.csv"' writer = csv.writer(response) writer.writerow(['nidn', 'nama']) writer.writerow(['12345678', 'Dr. Nama Dosen, M.Kom']) return response def dosen_import(request): if request.method == 'POST' and request.FILES.get('csv_file'): csv_file = request.FILES['csv_file'] decoded_file = csv_file.read().decode('utf-8').splitlines() reader = csv.DictReader(decoded_file) count = 0 for row in reader: Dosen.objects.update_or_create( nidn=row['nidn'], defaults={'nama': row['nama']} ) count += 1 messages.success(request, f"Berhasil mengimport {count} data Dosen.") return redirect('dosen_list') # --- MATA KULIAH --- def matkul_list(request): matkul_all = MataKuliah.objects.all() prodi_all = ProgramStudi.objects.all() if request.method == "POST": if "delete_massal" in request.POST: ids = request.POST.getlist("selected_ids") MataKuliah.objects.filter(id__in=ids).delete() messages.success(request, f"Berhasil menghapus {len(ids)} Mata Kuliah.") else: pk = request.POST.get("pk") kode = request.POST.get("kode") nama = request.POST.get("nama") sks = request.POST.get("sks") semester = request.POST.get("semester") prodi_id = request.POST.get("prodi") prodi = get_object_or_404(ProgramStudi, id=prodi_id) try: if pk: obj = get_object_or_404(MataKuliah, pk=pk) obj.kode = kode obj.nama = nama obj.sks = sks obj.semester = semester obj.prodi = prodi obj.save() messages.success(request, "Mata Kuliah berhasil diupdate.") else: MataKuliah.objects.create(kode=kode, nama=nama, sks=sks, semester=semester, prodi=prodi) messages.success(request, "Mata Kuliah berhasil ditambahkan.") except IntegrityError: messages.error(request, "Gagal menyimpan. Kode atau Nama Mata Kuliah sudah ada di Program Studi ini.") return redirect("matkul_list") return render(request, "core/matkul_list.html", {"matkul_all": matkul_all, "prodi_all": prodi_all}) def matkul_delete(request, pk): get_object_or_404(MataKuliah, pk=pk).delete() messages.success(request, "Mata Kuliah berhasil dihapus.") return redirect("matkul_list") def matkul_template(request): response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="template_matkul.csv"' writer = csv.writer(response) writer.writerow(['kode', 'nama', 'sks', 'semester', 'program_studi']) writer.writerow(['MK001', 'Algoritma Pemrograman', '3', '1', 'Teknik Informatika']) return response def matkul_import(request): if request.method == 'POST' and request.FILES.get('csv_file'): csv_file = request.FILES['csv_file'] decoded_file = csv_file.read().decode('utf-8').splitlines() reader = csv.DictReader(decoded_file) count = 0 for row in reader: prodi_nama = row.get('program_studi') prodi, _ = ProgramStudi.objects.get_or_create(nama=prodi_nama) MataKuliah.objects.update_or_create( kode=row['kode'], prodi=prodi, defaults={ 'nama': row['nama'], 'sks': row['sks'], 'semester': row['semester'] } ) count += 1 messages.success(request, f"Berhasil mengimport {count} data Mata Kuliah.") return redirect('matkul_list') # --- RUANGAN --- def ruangan_list(request): ruangan_all = Ruangan.objects.all() prodi_all = ProgramStudi.objects.all() if request.method == "POST": if "delete_massal" in request.POST: ids = request.POST.getlist("selected_ids") Ruangan.objects.filter(id__in=ids).delete() messages.success(request, f"Berhasil menghapus {len(ids)} Ruangan.") else: pk = request.POST.get("pk") nama = request.POST.get("nama") kapasitas = request.POST.get("kapasitas") prodi_id = request.POST.get("prodi") prodi = get_object_or_404(ProgramStudi, id=prodi_id) if prodi_id else None if pk: obj = get_object_or_404(Ruangan, pk=pk) obj.nama = nama obj.kapasitas = kapasitas obj.prodi = prodi obj.save() messages.success(request, "Ruangan berhasil diupdate.") else: Ruangan.objects.create(nama=nama, kapasitas=kapasitas, prodi=prodi) messages.success(request, "Ruangan berhasil ditambahkan.") return redirect("ruangan_list") return render(request, "core/ruangan_list.html", {"ruangan_all": ruangan_all, "prodi_all": prodi_all}) def ruangan_delete(request, pk): get_object_or_404(Ruangan, pk=pk).delete() messages.success(request, "Ruangan berhasil dihapus.") return redirect("ruangan_list") def ruangan_template(request): response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="template_ruangan.csv"' writer = csv.writer(response) writer.writerow(['nama', 'kapasitas', 'program_studi']) writer.writerow(['Ruang A1', '40', 'Teknik Informatika']) return response def ruangan_import(request): if request.method == 'POST' and request.FILES.get('csv_file'): csv_file = request.FILES['csv_file'] decoded_file = csv_file.read().decode('utf-8').splitlines() reader = csv.DictReader(decoded_file) count = 0 for row in reader: prodi_nama = row.get('program_studi') prodi = None if prodi_nama: prodi, _ = ProgramStudi.objects.get_or_create(nama=prodi_nama) Ruangan.objects.create(nama=row['nama'], kapasitas=row['kapasitas'], prodi=prodi) count += 1 messages.success(request, f"Berhasil mengimport {count} data Ruangan.") return redirect('ruangan_list') # --- KELAS --- def kelas_list(request): kelas_all = Kelas.objects.all() prodi_all = ProgramStudi.objects.all() if request.method == "POST": if "delete_massal" in request.POST: ids = request.POST.getlist("selected_ids") Kelas.objects.filter(id__in=ids).delete() messages.success(request, f"Berhasil menghapus {len(ids)} Kelas.") else: pk = request.POST.get("pk") nama = request.POST.get("nama") prodi_id = request.POST.get("prodi") prodi = get_object_or_404(ProgramStudi, id=prodi_id) if prodi_id else None if pk: obj = get_object_or_404(Kelas, pk=pk) obj.nama = nama obj.prodi = prodi obj.save() messages.success(request, "Kelas berhasil diupdate.") else: Kelas.objects.create(nama=nama, prodi=prodi) messages.success(request, "Kelas berhasil ditambahkan.") return redirect("kelas_list") return render(request, "core/kelas_list.html", {"kelas_all": kelas_all, "prodi_all": prodi_all}) def kelas_delete(request, pk): get_object_or_404(Kelas, pk=pk).delete() messages.success(request, "Kelas berhasil dihapus.") return redirect("kelas_list") def kelas_template(request): response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="template_kelas.csv"' writer = csv.writer(response) writer.writerow(['nama', 'program_studi']) writer.writerow(['Kelas A', 'Teknik Informatika']) return response def kelas_import(request): if request.method == 'POST' and request.FILES.get('csv_file'): csv_file = request.FILES['csv_file'] decoded_file = csv_file.read().decode('utf-8').splitlines() reader = csv.DictReader(decoded_file) count = 0 for row in reader: prodi_nama = row.get('program_studi') prodi = None if prodi_nama: prodi, _ = ProgramStudi.objects.get_or_create(nama=prodi_nama) Kelas.objects.create(nama=row['nama'], prodi=prodi) count += 1 messages.success(request, f"Berhasil mengimport {count} data Kelas.") return redirect('kelas_list') # --- JAM --- def jam_list(request): jam_all = Jam.objects.all() if request.method == "POST": if "delete_massal" in request.POST: ids = request.POST.getlist("selected_ids") Jam.objects.filter(id__in=ids).delete() messages.success(request, f"Berhasil menghapus {len(ids)} Jam.") else: pk = request.POST.get("pk") range_waktu = request.POST.get("range_waktu") if pk: obj = get_object_or_404(Jam, pk=pk) obj.range_waktu = range_waktu obj.save() messages.success(request, "Jam berhasil diupdate.") else: Jam.objects.create(range_waktu=range_waktu) messages.success(request, "Jam berhasil ditambahkan.") return redirect("jam_list") return render(request, "core/jam_list.html", {"jam_all": jam_all}) def jam_delete(request, pk): get_object_or_404(Jam, pk=pk).delete() messages.success(request, "Jam berhasil dihapus.") return redirect("jam_list") def jam_generate(request): if request.method == "POST": start_str = request.POST.get("jam_mulai") # e.g. "07:00" end_str = request.POST.get("jam_selesai") # e.g. "17:00" mins_per_sks = int(request.POST.get("durasi_per_sks", 50)) sks_per_slot = int(request.POST.get("jumlah_sks", 2)) jeda_mins = int(request.POST.get("jeda", 0)) start_time = datetime.datetime.strptime(start_str, "%H:%M") end_time = datetime.datetime.strptime(end_str, "%H:%M") slot_duration = mins_per_sks * sks_per_slot current_time = start_time count = 0 while current_time + datetime.timedelta(minutes=slot_duration) <= end_time: slot_end = current_time + datetime.timedelta(minutes=slot_duration) range_waktu = f"{current_time.strftime('%H:%M')} - {slot_end.strftime('%H:%M')}" Jam.objects.get_or_create(range_waktu=range_waktu) current_time = slot_end + datetime.timedelta(minutes=jeda_mins) count += 1 messages.success(request, f"Berhasil menggenerate {count} slot waktu.") return redirect("jam_list") # --- TAHUN AKADEMIK --- def tahun_list(request): tahun_all = TahunAkademik.objects.all() if request.method == "POST": if "delete_massal" in request.POST: ids = request.POST.getlist("selected_ids") TahunAkademik.objects.filter(id__in=ids).delete() messages.success(request, f"Berhasil menghapus {len(ids)} Tahun Akademik.") else: pk = request.POST.get("pk") nama = request.POST.get("nama") is_active = request.POST.get("is_active") == "on" if pk: obj = get_object_or_404(TahunAkademik, pk=pk) if is_active: TahunAkademik.objects.update(is_active=False) obj.nama = nama obj.is_active = is_active obj.save() messages.success(request, "Tahun Akademik berhasil diupdate.") else: if is_active: TahunAkademik.objects.update(is_active=False) TahunAkademik.objects.create(nama=nama, is_active=is_active) messages.success(request, "Tahun Akademik berhasil ditambahkan.") return redirect("tahun_list") return render(request, "core/tahun_list.html", {"tahun_all": tahun_all}) def tahun_delete(request, pk): get_object_or_404(TahunAkademik, pk=pk).delete() messages.success(request, "Tahun Akademik berhasil dihapus.") return redirect("tahun_list") # --- HARI --- def hari_list(request): hari_all = Hari.objects.all() if request.method == "POST": if "delete_massal" in request.POST: ids = request.POST.getlist("selected_ids") Hari.objects.filter(id__in=ids).delete() messages.success(request, f"Berhasil menghapus {len(ids)} Hari.") else: pk = request.POST.get("pk") nama = request.POST.get("nama") if pk: obj = get_object_or_404(Hari, pk=pk) obj.nama = nama obj.save() messages.success(request, "Hari berhasil diupdate.") else: Hari.objects.create(nama=nama) messages.success(request, "Hari berhasil ditambahkan.") return redirect("hari_list") return render(request, "core/hari_list.html", {"hari_all": hari_all}) def hari_delete(request, pk): get_object_or_404(Hari, pk=pk).delete() messages.success(request, "Hari berhasil dihapus.") return redirect("hari_list") # --- DOSEN PENGAMPU --- def pengampu_list(request): pengampu_all = DosenPengampu.objects.all() dosen_all = Dosen.objects.all() matkul_all = MataKuliah.objects.all() tahun_all = TahunAkademik.objects.all() prodi_all = ProgramStudi.objects.all() kelas_all = Kelas.objects.all() if request.method == "POST": if "delete_massal" in request.POST: ids = request.POST.getlist("selected_ids") DosenPengampu.objects.filter(id__in=ids).delete() messages.success(request, f"Berhasil menghapus {len(ids)} Dosen Pengampu.") else: pk = request.POST.get("pk") dosen_id = request.POST.get("dosen") matkul_id = request.POST.get("matkul") tahun_id = request.POST.get("tahun") prodi_id = request.POST.get("prodi") kelas_id = request.POST.get("kelas") dosen = get_object_or_404(Dosen, id=dosen_id) matkul = get_object_or_404(MataKuliah, id=matkul_id) tahun = get_object_or_404(TahunAkademik, id=tahun_id) prodi = get_object_or_404(ProgramStudi, id=prodi_id) if prodi_id else None kelas = get_object_or_404(Kelas, id=kelas_id) if kelas_id else None if pk: obj = get_object_or_404(DosenPengampu, pk=pk) obj.dosen = dosen obj.matkul = matkul obj.tahun_akademik = tahun obj.prodi = prodi obj.kelas = kelas obj.save() messages.success(request, "Dosen Pengampu berhasil diupdate.") else: DosenPengampu.objects.create( dosen=dosen, matkul=matkul, tahun_akademik=tahun, prodi=prodi, kelas=kelas ) messages.success(request, "Dosen Pengampu berhasil ditambahkan.") return redirect("pengampu_list") return render(request, "core/pengampu_list.html", { "pengampu_all": pengampu_all, "dosen_all": dosen_all, "matkul_all": matkul_all, "tahun_all": tahun_all, "prodi_all": prodi_all, "kelas_all": kelas_all, }) def pengampu_delete(request, pk): get_object_or_404(DosenPengampu, pk=pk).delete() messages.success(request, "Dosen Pengampu berhasil dihapus.") return redirect("pengampu_list") # --- DAFTAR HADIR --- def daftar_hadir_list(request): hadir_all = DaftarHadir.objects.all() jadwal_all = Jadwal.objects.all() if request.method == "POST": if "delete_massal" in request.POST: ids = request.POST.getlist("selected_ids") DaftarHadir.objects.filter(id__in=ids).delete() messages.success(request, f"Berhasil menghapus {len(ids)} Daftar Hadir.") else: pk = request.POST.get("pk") jadwal_id = request.POST.get("jadwal") tanggal = request.POST.get("tanggal") pertemuan = request.POST.get("pertemuan_ke") ket = request.POST.get("keterangan") jadwal = get_object_or_404(Jadwal, id=jadwal_id) if pk: obj = get_object_or_404(DaftarHadir, pk=pk) obj.jadwal = jadwal obj.tanggal = tanggal obj.pertemuan_ke = pertemuan obj.keterangan = ket obj.save() messages.success(request, "Daftar Hadir berhasil diupdate.") else: DaftarHadir.objects.create(jadwal=jadwal, tanggal=tanggal, pertemuan_ke=pertemuan, keterangan=ket) messages.success(request, "Daftar Hadir berhasil ditambahkan.") return redirect("daftar_hadir_list") return render(request, "core/daftar_hadir_list.html", {"hadir_all": hadir_all, "jadwal_all": jadwal_all}) def daftar_hadir_delete(request, pk): get_object_or_404(DaftarHadir, pk=pk).delete() messages.success(request, "Daftar Hadir berhasil dihapus.") return redirect("daftar_hadir_list") # --- JADWAL --- def jadwal_list(request): jadwal_all = Jadwal.objects.all().order_by('hari', 'jam') pengampu_all = DosenPengampu.objects.all() ruangan_all = Ruangan.objects.all() hari_all = Hari.objects.all() jam_all = Jam.objects.all() kelas_all = Kelas.objects.all() if request.method == "POST": if "delete_massal" in request.POST: ids = request.POST.getlist("selected_ids") Jadwal.objects.filter(id__in=ids).delete() messages.success(request, f"Berhasil menghapus {len(ids)} Jadwal.") else: pk = request.POST.get("pk") pengampu_id = request.POST.get("pengampu") ruangan_id = request.POST.get("ruangan") hari_id = request.POST.get("hari") jam_id = request.POST.get("jam") kelas_id = request.POST.get("kelas") pengampu = get_object_or_404(DosenPengampu, id=pengampu_id) ruangan = get_object_or_404(Ruangan, id=ruangan_id) hari = get_object_or_404(Hari, id=hari_id) jam = get_object_or_404(Jam, id=jam_id) kelas = get_object_or_404(Kelas, id=kelas_id) if pk: obj = get_object_or_404(Jadwal, pk=pk) obj.dosen_pengampu = pengampu obj.ruangan = ruangan obj.hari = hari obj.jam = jam obj.kelas = kelas obj.save() messages.success(request, "Jadwal berhasil diupdate.") else: Jadwal.objects.create( dosen_pengampu=pengampu, ruangan=ruangan, hari=hari, jam=jam, kelas=kelas, is_manual=True ) messages.success(request, "Jadwal berhasil ditambahkan secara manual.") return redirect("jadwal_list") return render(request, "core/jadwal_list.html", { "jadwal_all": jadwal_all, "pengampu_all": pengampu_all, "ruangan_all": ruangan_all, "hari_all": hari_all, "jam_all": jam_all, "kelas_all": kelas_all, }) def buat_jadwal(request): if request.method == "POST": pengampu_all = DosenPengampu.objects.all() ruangan_all = Ruangan.objects.all() hari_all = Hari.objects.all() jam_all = Jam.objects.all() if not (pengampu_all and ruangan_all and hari_all and jam_all): messages.error(request, "Data Master belum lengkap.") return redirect("buat_jadwal") # Only delete schedules that were not manually created Jadwal.objects.filter(is_manual=False).delete() # Get pengampu that don't have a schedule yet (including manual ones) pengampu_with_jadwal = Jadwal.objects.values_list('dosen_pengampu_id', flat=True) pending_pengampu = pengampu_all.exclude(id__in=pengampu_with_jadwal) for pengampu in pending_pengampu: # We use the class assigned in DosenPengampu kelas = pengampu.kelas if not kelas: # Fallback to random class if not assigned in Pengampu (for legacy data) kelas_all = Kelas.objects.all() kelas = random.choice(kelas_all) if kelas_all.exists() else None if not kelas: continue # Simple logic to find an available slot (avoiding collisions for manual ones) # This is still a basic random generator but it now respects existing manual entries max_tries = 50 for _ in range(max_tries): h = random.choice(hari_all) j = random.choice(jam_all) r = random.choice(ruangan_all) # Check for collisions in that room/time if not Jadwal.objects.filter(hari=h, jam=j, ruangan=r).exists() and \ not Jadwal.objects.filter(hari=h, jam=j, kelas=kelas).exists() and \ not Jadwal.objects.filter(hari=h, jam=j, dosen_pengampu__dosen=pengampu.dosen).exists(): Jadwal.objects.create( dosen_pengampu=pengampu, ruangan=r, hari=h, jam=j, kelas=kelas, is_manual=False ) break messages.success(request, "Jadwal berhasil digenerate otomatis (menghindari jadwal manual).") return redirect("jadwal_list") return render(request, "core/buat_jadwal.html") def jadwal_delete(request, pk): get_object_or_404(Jadwal, pk=pk).delete() messages.success(request, "Jadwal berhasil dihapus.") return redirect("jadwal_list") def jadwal_export_excel(request): wb = Workbook() ws = wb.active ws.title = "Jadwal Kuliah" headers = [ 'No', 'Hari', 'Jam', 'Kode MK', 'Mata Kuliah', 'SKS', 'Semester', 'Dosen', 'Kelas', 'Ruangan', 'Program Studi', 'Tahun Akademik' ] ws.append(headers) jadwal_all = Jadwal.objects.all().select_related( 'hari', 'jam', 'dosen_pengampu__matkul', 'dosen_pengampu__dosen', 'kelas', 'ruangan', 'dosen_pengampu__matkul__prodi', 'dosen_pengampu__tahun_akademik' ).order_by('hari', 'jam') for i, j in enumerate(jadwal_all, 1): ws.append([ i, j.hari.nama, j.jam.range_waktu, j.dosen_pengampu.matkul.kode, j.dosen_pengampu.matkul.nama, j.dosen_pengampu.matkul.sks, j.dosen_pengampu.matkul.semester, j.dosen_pengampu.dosen.nama, j.kelas.nama, j.ruangan.nama, j.dosen_pengampu.matkul.prodi.nama, j.dosen_pengampu.tahun_akademik.nama ]) response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') response['Content-Disposition'] = 'attachment; filename="jadwal_kuliah.xlsx"' wb.save(response) return response