af369883f3
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.
152 lines
5.5 KiB
Python
152 lines
5.5 KiB
Python
# -*- 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()
|