Tambah halaman RTM (Rancangan Tugas Mahasiswa) tersendiri, terpisah dari Nilai
Dokumen RTM resmi sesuai template baku prodi (FORM RPS & RTM UNTAN), diisi
di halaman /rps/{kode}/rtm sendiri setelah RPS ditandai selesai — blok Tugas
bisa ditambah/dihapus dinamis, dan bisa diunduh sebagai .docx. Murni
dokumentasi, tak menyimpan atau memengaruhi nilai/skor mahasiswa sama sekali.
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Generator Rancangan Tugas Mahasiswa (RTM) (.docx).
|
||||
Mengikuti format resmi `FORM RPS & RTM UNTAN (draft).docx`: tabel identitas
|
||||
(Mata Kuliah/Kode MK/Tugas ke/SKS/Dosen Pengampu/Semester) diikuti tabel
|
||||
RANCANGAN TUGAS MAHASISWA per tugas (Bentuk, Judul, Sub-CPMK, Deskripsi,
|
||||
Metode Pengerjaan, Bentuk & Format Luaran, Indikator/Kriteria/Bobot,
|
||||
Jadwal Pelaksanaan, Lain-lain, Daftar Rujukan). Dokumen ini berdiri sendiri,
|
||||
tak memuat data nilai/skor mahasiswa sama sekali.
|
||||
"""
|
||||
import io
|
||||
import os
|
||||
|
||||
from docx import Document
|
||||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||
from docx.oxml import OxmlElement
|
||||
from docx.oxml.ns import qn
|
||||
from docx.shared import Cm, Pt, RGBColor
|
||||
|
||||
DARK_BLUE = "1F4E79"
|
||||
LIGHT_BLUE = "D6E4F0"
|
||||
WHITE = "FFFFFF"
|
||||
|
||||
FAKULTAS = os.environ.get("FAKULTAS", "FAKULTAS TEKNIK")
|
||||
PRODI = os.environ.get("PRODI", "PROGRAM STUDI INFORMATIKA")
|
||||
|
||||
|
||||
def _shd(cell, hex_color):
|
||||
tc_pr = cell._tc.get_or_add_tcPr()
|
||||
el = OxmlElement("w:shd")
|
||||
el.set(qn("w:val"), "clear")
|
||||
el.set(qn("w:color"), "auto")
|
||||
el.set(qn("w:fill"), hex_color)
|
||||
tc_pr.append(el)
|
||||
|
||||
|
||||
def _ct(cell, text, bold=False, italic=False, size=10, color=None,
|
||||
align=WD_ALIGN_PARAGRAPH.LEFT):
|
||||
p = cell.paragraphs[0]
|
||||
p.alignment = align
|
||||
p.paragraph_format.space_before = Pt(2)
|
||||
p.paragraph_format.space_after = Pt(2)
|
||||
run = p.add_run()
|
||||
for i, line in enumerate(str(text or "").split("\n")):
|
||||
if i:
|
||||
run.add_break()
|
||||
run.add_text(line)
|
||||
run.bold = bold
|
||||
run.italic = italic
|
||||
run.font.name = "Times New Roman"
|
||||
run.font.size = Pt(size)
|
||||
if color:
|
||||
run.font.color.rgb = RGBColor.from_string(color)
|
||||
|
||||
|
||||
def _hdr(cell, text, size=10):
|
||||
_shd(cell, DARK_BLUE)
|
||||
_ct(cell, text, bold=True, color=WHITE, size=size, align=WD_ALIGN_PARAGRAPH.CENTER)
|
||||
|
||||
|
||||
def _borders(table):
|
||||
tbl_pr = table._tbl.tblPr
|
||||
el = OxmlElement("w:tblBorders")
|
||||
for sisi in ("top", "left", "bottom", "right", "insideH", "insideV"):
|
||||
b = OxmlElement(f"w:{sisi}")
|
||||
b.set(qn("w:val"), "single")
|
||||
b.set(qn("w:sz"), "4")
|
||||
b.set(qn("w:color"), "000000")
|
||||
el.append(b)
|
||||
tbl_pr.append(el)
|
||||
|
||||
|
||||
def build_rtm(d: dict) -> bytes:
|
||||
doc = Document()
|
||||
for sec in doc.sections:
|
||||
sec.top_margin = sec.bottom_margin = Cm(2.5)
|
||||
sec.left_margin = sec.right_margin = Cm(2.5)
|
||||
|
||||
def para(text, size=12, bold=False, align=WD_ALIGN_PARAGRAPH.LEFT, before=6, after=3):
|
||||
p = doc.add_paragraph()
|
||||
p.alignment = align
|
||||
p.paragraph_format.space_before = Pt(before)
|
||||
p.paragraph_format.space_after = Pt(after)
|
||||
r = p.add_run(text)
|
||||
r.bold = bold
|
||||
r.font.name = "Times New Roman"
|
||||
r.font.size = Pt(size)
|
||||
return p
|
||||
|
||||
para("RANCANGAN TUGAS MAHASISWA (RTM)", size=16, bold=True, align=WD_ALIGN_PARAGRAPH.CENTER)
|
||||
para(PRODI, size=12, bold=True, align=WD_ALIGN_PARAGRAPH.CENTER, before=0)
|
||||
para(f"{FAKULTAS} — UNIVERSITAS TANJUNGPURA", size=11, align=WD_ALIGN_PARAGRAPH.CENTER, before=0)
|
||||
para(f"Tahun Ajaran {d.get('tahun', '')}", size=11, align=WD_ALIGN_PARAGRAPH.CENTER,
|
||||
before=0, after=10)
|
||||
|
||||
daftar_tugas = d.get("tugas", [])
|
||||
if not daftar_tugas:
|
||||
para("Belum ada Tugas yang diisi untuk RTM ini.", after=6)
|
||||
|
||||
for idx, t in enumerate(daftar_tugas, 1):
|
||||
if idx > 1:
|
||||
doc.add_page_break()
|
||||
|
||||
ti = doc.add_table(rows=3, cols=4)
|
||||
_borders(ti)
|
||||
baris = [
|
||||
("Mata Kuliah", d.get("nama", ""), "Kode MK", d.get("kode", "")),
|
||||
("Tugas ke", str(idx), "SKS", str(d.get("sks", "") or "—")),
|
||||
("Dosen Pengampu", d.get("pengampu", "") or "—", "Semester",
|
||||
str(d.get("semester", "") or "—")),
|
||||
]
|
||||
for ri, (l1, v1, l2, v2) in enumerate(baris):
|
||||
_shd(ti.cell(ri, 0), LIGHT_BLUE)
|
||||
_ct(ti.cell(ri, 0), l1, bold=True)
|
||||
_ct(ti.cell(ri, 1), v1)
|
||||
_shd(ti.cell(ri, 2), LIGHT_BLUE)
|
||||
_ct(ti.cell(ri, 2), l2, bold=True)
|
||||
_ct(ti.cell(ri, 3), v2)
|
||||
doc.add_paragraph()
|
||||
|
||||
subs_teks = ", ".join(t.get("subs") or []) or "—"
|
||||
jadwal = "; ".join(x for x in (
|
||||
f"Alokasi waktu: {t['alokasi_waktu']}" if t.get("alokasi_waktu") else "",
|
||||
f"Tanggal: {t['tanggal']}" if t.get("tanggal") else "",
|
||||
) if x) or "—"
|
||||
rows = [
|
||||
("BENTUK TUGAS", t.get("bentuk_tugas", "") or "—"),
|
||||
("JUDUL TUGAS", t.get("judul", "") or "—"),
|
||||
("SUB-CPMK YANG DICAPAI", subs_teks),
|
||||
("DESKRIPSI TUGAS", t.get("deskripsi", "") or "—"),
|
||||
("METODE PENGERJAAN TUGAS", t.get("metode_pengerjaan", "") or "—"),
|
||||
("BENTUK DAN FORMAT LUARAN", t.get("luaran", "") or "—"),
|
||||
("INDIKATOR, KRITERIA DAN BOBOT PENILAIAN", t.get("indikator_bobot", "") or "—"),
|
||||
("JADWAL PELAKSANAAN", jadwal),
|
||||
("LAIN-LAIN", t.get("lain_lain", "") or "—"),
|
||||
("DAFTAR RUJUKAN", t.get("rujukan", "") or "—"),
|
||||
]
|
||||
tt = doc.add_table(rows=1 + len(rows), cols=2)
|
||||
_borders(tt)
|
||||
_hdr(tt.cell(0, 0), "RANCANGAN TUGAS MAHASISWA")
|
||||
tt.cell(0, 0).merge(tt.cell(0, 1))
|
||||
for ri, (label, isi) in enumerate(rows, 1):
|
||||
_shd(tt.cell(ri, 0), LIGHT_BLUE)
|
||||
_ct(tt.cell(ri, 0), label, bold=True, size=9)
|
||||
_ct(tt.cell(ri, 1), isi, size=10)
|
||||
tt.rows[ri].cells[0].width = Cm(5.5)
|
||||
tt.rows[ri].cells[1].width = Cm(11)
|
||||
|
||||
buf = io.BytesIO()
|
||||
doc.save(buf)
|
||||
return buf.getvalue()
|
||||
Reference in New Issue
Block a user