Files
sistem-desa-mandiri/generate_erd.py
amaliadwiy 2b71c729ad feat: tambah model ProjectTaskFile dan DivisionProjectTaskFile
Menambahkan relasi file ke task pada project dan division project.
2026-05-06 10:54:12 +08:00

215 lines
8.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.patches import FancyBboxPatch
import matplotlib.patheffects as pe
# ── colour palette ──────────────────────────────────────────────────────────
C = {
"admin": "#4A90D9",
"user": "#2ECC71",
"village": "#E74C3C",
"announce": "#F39C12",
"project": "#9B59B6",
"division": "#1ABC9C",
"discuss": "#E67E22",
"other": "#95A5A6",
"new": "#E74C3C",
}
GROUPS = {
"Admin": (["AdminRole","Admin"], C["admin"]),
"User": (["UserRole","User","TokenDeviceUser","UserLog"],C["user"]),
"Village": (["Village","ColorTheme","BannerImage"], C["village"]),
"Announcement": (["Announcement","AnnouncementMember",
"AnnouncementFile"], C["announce"]),
"Project": (["Project","ProjectMember","ProjectFile",
"ProjectLink","ProjectTask","ProjectTaskFile",
"ProjectTaskDetail"], C["project"]),
"Division": (["Division","DivisionMember","DivisionProject",
"DivisionProjectMember","DivisionProjectFile",
"DivisionProjectLink",
"DivisionProjectTask","DivisionProjectTaskFile",
"DivisionProjectTaskDetail",
"DivisionDisscussion","DivisionDisscussionComment",
"DivisionDiscussionFile",
"DivisionDocumentFolderFile","DivisionDocumentShare",
"DivisionCalendar","DivisionCalendarReminder",
"DivisionCalendarMember",
"ContainerFileDivision"], C["division"]),
"Discussion": (["Discussion","DiscussionMember",
"DiscussionComment","DiscussionFile"], C["discuss"]),
"Other": (["Group","Position","Notifications",
"Subscribe","Setting","ContainerImage"], C["other"]),
}
NEW_MODELS = {"ProjectTaskFile", "DivisionProjectTaskFile"}
# ── relations (src, dst, label) ─────────────────────────────────────────────
RELATIONS = [
# Admin
("AdminRole","Admin","1-N"),
# User
("UserRole","User","1-N"),
("Village","User","1-N"),
("Group","User","1-N"),
("Position","User","0..1-N"),
# Village
("Village","Group","1-N"),
("Village","Announcement","1-N"),
("Village","Project","1-N"),
("Village","Division","1-N"),
("Village","Discussion","1-N"),
("Village","ColorTheme","1-N"),
("Village","BannerImage","1-N"),
# Group
("Group","Position","1-N"),
("Group","Project","1-N"),
("Group","Division","1-N"),
("Group","AnnouncementMember","1-N"),
("Group","Discussion","1-N"),
# Announcement
("Announcement","AnnouncementMember","1-N"),
("Announcement","AnnouncementFile","1-N"),
("Division","AnnouncementMember","1-N"),
# Project
("Project","ProjectMember","1-N"),
("Project","ProjectFile","1-N"),
("Project","ProjectLink","1-N"),
("Project","ProjectTask","1-N"),
("ProjectTask","ProjectTaskDetail","1-N"),
("ProjectTask","ProjectTaskFile","1-N"),
("ProjectFile","ProjectTaskFile","1-N"),
# Division
("Division","DivisionMember","1-N"),
("Division","DivisionProject","1-N"),
("DivisionProject","DivisionProjectMember","1-N"),
("DivisionProject","DivisionProjectFile","1-N"),
("DivisionProject","DivisionProjectLink","1-N"),
("DivisionProject","DivisionProjectTask","1-N"),
("DivisionProjectTask","DivisionProjectTaskDetail","1-N"),
("DivisionProjectTask","DivisionProjectTaskFile","1-N"),
("DivisionProjectFile","DivisionProjectTaskFile","1-N"),
("ContainerFileDivision","DivisionProjectFile","1-N"),
("Division","DivisionDisscussion","1-N"),
("DivisionDisscussion","DivisionDisscussionComment","1-N"),
("DivisionDisscussion","DivisionDiscussionFile","1-N"),
("Division","DivisionDocumentFolderFile","1-N"),
("DivisionDocumentFolderFile","DivisionDocumentShare","1-N"),
("Division","DivisionCalendar","1-N"),
("DivisionCalendar","DivisionCalendarReminder","1-N"),
("DivisionCalendar","DivisionCalendarMember","1-N"),
# Discussion
("Discussion","DiscussionMember","1-N"),
("Discussion","DiscussionComment","1-N"),
("Discussion","DiscussionFile","1-N"),
# Other
("User","Notifications","1-N"),
("User","Subscribe","1-1"),
("User","TokenDeviceUser","1-N"),
("User","UserLog","1-N"),
]
# ── layout: group boxes ──────────────────────────────────────────────────────
# (x, y, w, h) in data coordinates (canvas = 0..100 x 0..100)
LAYOUT = {
"Admin": ( 1, 88, 18, 10),
"User": ( 1, 68, 22, 18),
"Village": (26, 88, 22, 10),
"Other": (51, 88, 22, 10),
"Announcement": (76, 80, 22, 18),
"Project": ( 1, 2, 38, 48),
"Division": (41, 2, 38, 64),
"Discussion": (81, 2, 17, 30),
}
def group_center(gname):
x,y,w,h = LAYOUT[gname]
return x+w/2, y+h/2
def model_pos(model):
for gname,(models,_) in GROUPS.items():
if model in models:
x,y,w,h = LAYOUT[gname]
idx = models.index(model)
n = len(models)
cols = max(1, min(3, n))
rows = (n + cols - 1) // cols
col = idx % cols
row = idx // cols
mx = x + 1.5 + col * (w-2) / cols
my = y + h - 2.5 - row * (h-1.5) / rows
return mx, my
return 50, 50
# ── draw ─────────────────────────────────────────────────────────────────────
fig, ax = plt.subplots(figsize=(28, 22))
ax.set_xlim(0, 100)
ax.set_ylim(0, 100)
ax.axis("off")
fig.patch.set_facecolor("#F0F4F8")
ax.set_facecolor("#F0F4F8")
ax.set_title("ERD Sistem Desa Mandiri", fontsize=20, fontweight="bold",
color="#2C3E50", pad=14)
# group boxes
for gname, (models, color) in GROUPS.items():
x,y,w,h = LAYOUT[gname]
rect = FancyBboxPatch((x,y), w, h,
boxstyle="round,pad=0.3",
linewidth=2, edgecolor=color,
facecolor=color+"22")
ax.add_patch(rect)
ax.text(x+w/2, y+h-0.6, gname, ha="center", va="top",
fontsize=9, fontweight="bold", color=color)
# model nodes
for gname, (models, color) in GROUPS.items():
for m in models:
mx, my = model_pos(m)
is_new = m in NEW_MODELS
fc = "#FFECEC" if is_new else "white"
ec = C["new"] if is_new else color
lw = 2.5 if is_new else 1.5
node = FancyBboxPatch((mx-3.2, my-0.85), 6.4, 1.7,
boxstyle="round,pad=0.2",
linewidth=lw, edgecolor=ec, facecolor=fc)
ax.add_patch(node)
fw = "bold" if is_new else "normal"
ax.text(mx, my, m, ha="center", va="center",
fontsize=6.2, color="#2C3E50", fontweight=fw)
# relations
drawn = set()
for src, dst, lbl in RELATIONS:
key = tuple(sorted([src,dst]))
sx, sy = model_pos(src)
dx, dy = model_pos(dst)
color = "#BDC3C7"
is_new_rel = src in NEW_MODELS or dst in NEW_MODELS
if is_new_rel:
color = C["new"]
ax.annotate("", xy=(dx,dy), xytext=(sx,sy),
arrowprops=dict(arrowstyle="-|>", color=color,
lw=1.5 if is_new_rel else 0.8,
connectionstyle="arc3,rad=0.05"))
if key not in drawn:
mx2, my2 = (sx+dx)/2, (sy+dy)/2
ax.text(mx2, my2+0.4, lbl, ha="center", va="bottom",
fontsize=4.5, color=color, alpha=0.85)
drawn.add(key)
# legend
leg_items = [
mpatches.Patch(facecolor="#FFECEC", edgecolor=C["new"], linewidth=2,
label="Model Baru"),
mpatches.Patch(facecolor="white", edgecolor="#BDC3C7", label="Model Lama"),
]
ax.legend(handles=leg_items, loc="lower right", fontsize=9,
framealpha=0.9, edgecolor="#BDC3C7")
out = "/Users/wibu04/Documents/Projects/sistem-desa-mandiri/erd.png"
plt.savefig(out, dpi=150, bbox_inches="tight", facecolor=fig.get_facecolor())
plt.close()
print("Saved:", out)