Skip to content

Commit

Permalink
lint + merge migrations
Browse files Browse the repository at this point in the history
  • Loading branch information
AaDalal committed Feb 7, 2024
1 parent 730c015 commit 5f5573e
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 86 deletions.

This file was deleted.

52 changes: 52 additions & 0 deletions backend/degree/migrations/0007_auto_20240207_0105.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Generated by Django 3.2.23 on 2024-02-07 06:05

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('courses', '0061_merge_20231112_1524'),
('degree', '0006_auto_20240205_1950'),
]

operations = [
migrations.AlterField(
model_name='degreeplan',
name='degrees',
field=models.ManyToManyField(help_text='The degrees this degree plan is associated with.', to='degree.Degree'),
),
migrations.AlterField(
model_name='degreeplan',
name='person',
field=models.ForeignKey(help_text='The user the degree plan belongs to.', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='doublecountrestriction',
name='max_courses',
field=models.PositiveSmallIntegerField(help_text='\nThe maximum number of courses you can count for both rules.\nIf null, there is no limit, and max_credits must not be null.\n', null=True),
),
migrations.AlterField(
model_name='doublecountrestriction',
name='max_credits',
field=models.DecimalField(decimal_places=2, help_text='\nThe maximum number of CUs you can count for both rules.\nIf null, there is no limit, and max_credits must not be null.\n', max_digits=4, null=True),
),
migrations.AlterField(
model_name='doublecountrestriction',
name='rule',
field=models.ForeignKey(help_text='\nA rule in the double count restriction.\n', on_delete=django.db.models.deletion.CASCADE, related_name='+', to='degree.rule'),
),
migrations.AlterField(
model_name='fulfillment',
name='historical_course',
field=models.ForeignKey(help_text='\nThe last offering of the course with the full code, or null if\nthere is no such historical course.\n', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='courses.course'),
),
migrations.AlterField(
model_name='rule',
name='q',
field=models.TextField(blank=True, help_text='\nString representing a Q() object that returns the set of courses\nsatisfying this rule. Non-empty iff this is a Rule leaf.\nThis Q object is expected to be normalized before it is serialized\nto a string.\n', max_length=1000),
),
]
143 changes: 77 additions & 66 deletions backend/degree/static/pdp/degree-editor.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import React, { useCallback, useEffect } from "https://cdn.jsdelivr.net/npm/[email protected]/+esm";
import React, {
useCallback,
useEffect,
} from "https://cdn.jsdelivr.net/npm/[email protected]/+esm";
import ReactDOM from "https://cdn.jsdelivr.net/npm/[email protected]/+esm";
import ReactFlow, {
ConnectionLineType,
useNodesState,
useEdgesState,
Controls,
Background,
Panel
Panel,
} from "https://cdn.jsdelivr.net/npm/[email protected]/+esm";
import dagre from "https://esm.sh/[email protected]";

Expand All @@ -16,32 +19,29 @@ const params = new Proxy(new URLSearchParams(window.location.search), {
const id = Number(params.id);
const renderRule = (rule) => {
return (
<div style={{display: "flex", flexDirection: "column", gap: ".5em"}}>
<div style={{ display: "flex", flexDirection: "column", gap: ".5em" }}>
<div>{rule.id}</div>
<div style={{ fontWeight: "bold" }}>{rule.title || "<No title>"}</div>
<div>Q: {rule.q}</div>
<div>Num: {rule.num}</div>
<div>Credits: {rule.credits}</div>
</div>
)
);
};

const nodeWidth = 172;
const nodeHeight = 300;
const dagreGraph = new dagre.graphlib.Graph();
dagreGraph.setDefaultEdgeLabel(() => ({}));
const getLayoutedElements = (nodes, edges, direction = 'TB') => {
const isHorizontal = direction === 'LR';
const getLayoutedElements = (nodes, edges, direction = "TB") => {
const isHorizontal = direction === "LR";
dagreGraph.setGraph({ rankdir: direction });

nodes.forEach((node) => {
dagreGraph.setNode(
node.id,
{
width: nodeWidth,
height: nodeHeight
}
);
dagreGraph.setNode(node.id, {
width: nodeWidth,
height: nodeHeight,
});
});

edges.forEach((edge) => {
Expand All @@ -52,8 +52,8 @@ const getLayoutedElements = (nodes, edges, direction = 'TB') => {

nodes.forEach((node) => {
const nodeWithPosition = dagreGraph.node(node.id);
node.targetPosition = isHorizontal ? 'left' : 'top';
node.sourcePosition = isHorizontal ? 'right' : 'bottom';
node.targetPosition = isHorizontal ? "left" : "top";
node.sourcePosition = isHorizontal ? "right" : "bottom";

// We are shifting the dagre node position (anchor=center center) to the top left
// so it matches the React Flow node anchor point (top left).
Expand All @@ -68,50 +68,50 @@ const getLayoutedElements = (nodes, edges, direction = 'TB') => {
return { nodes, edges };
};

const pkOfNodeId = (nodeId) => [nodeId.startsWith("d"), Number(nodeId.slice(1))]
const pkOfNodeId = (nodeId) => [
nodeId.startsWith("d"),
Number(nodeId.slice(1)),
];

const LayoutFlow = () => {
const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);

const onConnect = useCallback(
(params) => {
console.log("onConnect", params)
if (params.source === params.target) return;
const [sourceIsDegree, sourceId] = pkOfNodeId(params.source);
const [targetIsDegree, targetId] = pkOfNodeId(params.target);
if (sourceIsDegree || targetIsDegree) return;
console.log("HERE")
const redirect = `/admin/degree/doublecountrestriction/add/?rule=${sourceId}&other_rule=${targetId}`;
window.location.href = redirect;
},
[]
);

const onEdgeDelete = useCallback(
(edge) => {
if (!edge.id.startsWith("c")) return;
},
[]
)

const onConnect = useCallback((params) => {
console.log("onConnect", params);
if (params.source === params.target) return;
const [sourceIsDegree, sourceId] = pkOfNodeId(params.source);
const [targetIsDegree, targetId] = pkOfNodeId(params.target);
if (sourceIsDegree || targetIsDegree) return;
console.log("HERE");
const redirect = `/admin/degree/doublecountrestriction/add/?rule=${sourceId}&other_rule=${targetId}`;
window.location.href = redirect;
}, []);

const onEdgeDelete = useCallback((edge) => {
if (!edge.id.startsWith("c")) return;
}, []);

useEffect(() => {
const fetchDegree = async () => {
if (!id) return;
const degree = await fetch(`/api/degree/degrees/${id}/`).then(response => response.json());

const degree = await fetch(`/api/degree/degrees/${id}/`).then(
(response) => response.json()
);

// get the nodes: the rules + the top level node
const root = {
id: "d" + degree.id,
type: "default",
const root = {
id: "d" + degree.id,
type: "default",
width: 300,
data: { label: `${degree.program} ${degree.degree} in ${degree.major} with conc. ${degree.concentration} (${degree.year})` },
data: {
label: `${degree.program} ${degree.degree} in ${degree.major} with conc. ${degree.concentration} (${degree.year})`,
},
style: {
background: "lightblue"
}
background: "lightblue",
},
};

const nodes = [];
const edges = [];
const stack = degree.rules.slice();
Expand All @@ -121,43 +121,45 @@ const LayoutFlow = () => {
nodes.push({
id,
type: "default",
data: {
data: {
label: renderRule(rule),
},
position: { x: 0, y: 0},
position: { x: 0, y: 0 },
width: 300,
});
const source = rule.parent ? `r${rule.parent}` : `d${degree.id}`
const source = rule.parent ? `r${rule.parent}` : `d${degree.id}`;
if (source) {
edges.push({
source: source,
edges.push({
source: source,
target: id,
id: `e${source.id}-${id}`,
type: "smoothstep",
})
};
});
}
rule.rules.forEach((subrule) => stack.push(subrule));
}

for (const doubleCountRestriction of degree.double_count_restrictions || []) {
for (const doubleCountRestriction of degree.double_count_restrictions ||
[]) {
const source = `r${doubleCountRestriction.rule}`;
const target = `r${doubleCountRestriction.other_rule}`;
edges.push({
source,
edges.push({
source,
target,
id: `c${source}-${target}`,
type: "smoothstep",
animated: true,
style: { stroke: "red" }
style: { stroke: "red" },
});
}

const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements([root, ...nodes], edges);
const { nodes: layoutedNodes, edges: layoutedEdges } =
getLayoutedElements([root, ...nodes], edges);
setNodes(layoutedNodes);
setEdges(layoutedEdges);
}
};
fetchDegree();
}, [])
}, []);

return (
<ReactFlow
Expand All @@ -173,10 +175,20 @@ const LayoutFlow = () => {
>
<Controls />
<Background variant="dots" gap={50} size={1} />
<Panel position="top-right" style={{ display: "flex", flexDirection: "column", gap: ".5em", padding: "1em", backgroundColor: "rgba(0, 0, 0, 0.4)"}}>
<a href={`${window.location.pathname}?id=${id+1}`}>Next Degree</a>
{ id > 1 && <a href={`${window.location.pathname}?id=${id-1}`}>Prev Degree</a>
}
<Panel
position="top-right"
style={{
display: "flex",
flexDirection: "column",
gap: ".5em",
padding: "1em",
backgroundColor: "rgba(0, 0, 0, 0.4)",
}}
>
<a href={`${window.location.pathname}?id=${id + 1}`}>Next Degree</a>
{id > 1 && (
<a href={`${window.location.pathname}?id=${id - 1}`}>Prev Degree</a>
)}
</Panel>
</ReactFlow>
);
Expand All @@ -190,5 +202,4 @@ const App = () => {
);
};

ReactDOM.render(<App />, document.getElementById('app'));

ReactDOM.render(<App />, document.getElementById("app"));

0 comments on commit 5f5573e

Please sign in to comment.