38259-vm/core/views.py
Flatlogic Bot 994bb04de0 SJP Admin
2026-02-07 05:16:18 +00:00

652 lines
27 KiB
Python

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