@@ -24,7 +24,7 @@ def check_data(data, required_fields):
|
|||||||
def instructor_required(func):
|
def instructor_required(func):
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def dec(*args, **kwargs):
|
def dec(*args, **kwargs):
|
||||||
if current_user.role != "instructor":
|
if not current_user.role in ["instructor", "admin"]:
|
||||||
return error_response(400, "User is not instructor!")
|
return error_response(400, "User is not instructor!")
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
@@ -95,6 +95,7 @@ def register():
|
|||||||
|
|
||||||
@bp.route("/course", methods=["POST"])
|
@bp.route("/course", methods=["POST"])
|
||||||
@login_required
|
@login_required
|
||||||
|
@admin_required
|
||||||
def create_course():
|
def create_course():
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
|
|
||||||
@@ -125,12 +126,60 @@ def create_course():
|
|||||||
return jsonify(c.to_dict())
|
return jsonify(c.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/course/<string:username>", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
@admin_required
|
||||||
|
def create_course_by_username(username):
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
required_fields = ["name", "course_code", "description"]
|
||||||
|
|
||||||
|
u = User.query.filter_by(username=username).first()
|
||||||
|
if not u:
|
||||||
|
return error_response(400, f"User with username {username} does not exist")
|
||||||
|
|
||||||
|
if f := check_data(data, required_fields):
|
||||||
|
return error_response(400, f"Must supply {f}")
|
||||||
|
|
||||||
|
c = Course.query.filter_by(course_code=data["course_code"]).first()
|
||||||
|
if c:
|
||||||
|
return error_response(
|
||||||
|
400, f"Course with course code {data['course_code']} already exists"
|
||||||
|
)
|
||||||
|
|
||||||
|
if u.role != "instructor":
|
||||||
|
return error_response(400, "User is not instructor")
|
||||||
|
|
||||||
|
data["instructor"] = str(u.id)
|
||||||
|
c = Course()
|
||||||
|
c.from_dict(data)
|
||||||
|
u.enroll(c)
|
||||||
|
db.session.add(c)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify(c.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/course/<int:id>", methods=["DELETE"])
|
||||||
|
@login_required
|
||||||
|
@admin_required
|
||||||
|
def delete_course(id):
|
||||||
|
c = Course.query.get(id)
|
||||||
|
if not c:
|
||||||
|
return error_response(400, f"Course with id {id} does not exist")
|
||||||
|
|
||||||
|
db.session.delete(c)
|
||||||
|
db.session.commit()
|
||||||
|
return jsonify(c.to_dict())
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/user/<int:id>/courses", methods=["GET"])
|
@bp.route("/user/<int:id>/courses", methods=["GET"])
|
||||||
@login_required
|
@login_required
|
||||||
def get_courses(id):
|
def get_courses(id):
|
||||||
u = User.query.get(id)
|
u = User.query.get(id)
|
||||||
d = {"courses": []}
|
d = {"courses": []}
|
||||||
for c in u.enrolled_courses.all():
|
courses = Course.query.all() if u.role == "admin" else u.enrolled_courses.all()
|
||||||
|
for c in courses:
|
||||||
d["courses"].append(c.to_dict())
|
d["courses"].append(c.to_dict())
|
||||||
resp = jsonify(d)
|
resp = jsonify(d)
|
||||||
return resp
|
return resp
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const MyNavbar = () => {
|
|||||||
<Navbar.Collapse id="navbar-nav">
|
<Navbar.Collapse id="navbar-nav">
|
||||||
<Nav className="ms-auto">
|
<Nav className="ms-auto">
|
||||||
<MyLink href="/">Home</MyLink>
|
<MyLink href="/">Home</MyLink>
|
||||||
{currentUser?.role === "instructor" &&
|
{(currentUser?.role === "instructor" || currentUser?.role === "admin") &&
|
||||||
instructorLinks.map((item, k) => {
|
instructorLinks.map((item, k) => {
|
||||||
return <MyLink key={k} href={item.link}>{item.label}</MyLink>;
|
return <MyLink key={k} href={item.link}>{item.label}</MyLink>;
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import React from "react";
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { Container, Table } from "react-bootstrap";
|
import { Form, Button, Container, Table } from "react-bootstrap";
|
||||||
import { Link } from "wouter";
|
import { Link } from "wouter";
|
||||||
import MyNavbar from "../components/MyNavbar";
|
import MyNavbar from "../components/MyNavbar";
|
||||||
import UserContext from "../contexts/UserContext";
|
import UserContext from "../contexts/UserContext";
|
||||||
@@ -8,6 +9,89 @@ import { makeRequest } from "../utils.ts";
|
|||||||
const ManagePage = () => {
|
const ManagePage = () => {
|
||||||
const [courseData, setCourseData] = useState([]);
|
const [courseData, setCourseData] = useState([]);
|
||||||
const { currentUser } = useContext(UserContext);
|
const { currentUser } = useContext(UserContext);
|
||||||
|
const [showAddCourseForm, setShowAddCourseForm] = useState(false);
|
||||||
|
|
||||||
|
const AddCourseForm = () => {
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
const [description, setDescription] = useState("");
|
||||||
|
const [username, setUsername] = useState("");
|
||||||
|
const [coursecode, setCoursecode] = useState("");
|
||||||
|
|
||||||
|
const submitCourseForm = () => {
|
||||||
|
makeRequest({
|
||||||
|
endpoint: `course/${username}`,
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
course_code: coursecode,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((resp) => resp.json())
|
||||||
|
.then((data) => {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Form
|
||||||
|
className="my-4 p-2 border justify-between items-center"
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
submitCourseForm();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form.Group controlId="courseCode">
|
||||||
|
<Form.Label>Course code</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter course code"
|
||||||
|
onChange={(e) => setCoursecode(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group controlId="courseName">
|
||||||
|
<Form.Label>Name</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter course name"
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group controlId="courseDescription">
|
||||||
|
<Form.Label>Description</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter course description"
|
||||||
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group controlId="courseInstructor">
|
||||||
|
<Form.Label>Instructor Username</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="text"
|
||||||
|
placeholder="Instructor"
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
<Button className="my-2" type="submit" variant="success">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendDeleteCourseRequest = (cid) => {
|
||||||
|
makeRequest({
|
||||||
|
endpoint: `course/${cid}`,
|
||||||
|
method: "DELETE",
|
||||||
|
})
|
||||||
|
.then((resp) => resp.json())
|
||||||
|
.then((data) => {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
makeRequest({
|
makeRequest({
|
||||||
@@ -28,6 +112,18 @@ const ManagePage = () => {
|
|||||||
<MyNavbar />
|
<MyNavbar />
|
||||||
<Container className="p-5 border">
|
<Container className="p-5 border">
|
||||||
<h1 className="mb-4">Manage Courses</h1>
|
<h1 className="mb-4">Manage Courses</h1>
|
||||||
|
{currentUser.role === "admin" && (
|
||||||
|
<Button
|
||||||
|
className="my-3"
|
||||||
|
variant="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setShowAddCourseForm(!showAddCourseForm);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(showAddCourseForm && "-") || "+"} Add course
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{showAddCourseForm && <AddCourseForm />}
|
||||||
<Table bordered striped hover>
|
<Table bordered striped hover>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -59,6 +155,17 @@ const ManagePage = () => {
|
|||||||
<Link href={`/manage/${course.id}/assignments`}>
|
<Link href={`/manage/${course.id}/assignments`}>
|
||||||
Assignments
|
Assignments
|
||||||
</Link>
|
</Link>
|
||||||
|
{currentUser?.role === "admin" && (
|
||||||
|
<React.Fragment>
|
||||||
|
<br />
|
||||||
|
<Button
|
||||||
|
variant="danger"
|
||||||
|
onClick={() => sendDeleteCourseRequest(course.id)}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</React.Fragment>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ const RegisterPage = () => {
|
|||||||
>
|
>
|
||||||
<option value="student">Student</option>
|
<option value="student">Student</option>
|
||||||
<option value="instructor">Instructor</option>
|
<option value="instructor">Instructor</option>
|
||||||
|
<option value="admin">Admin</option>
|
||||||
</Form.Select>
|
</Form.Select>
|
||||||
</Col>
|
</Col>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
|
|||||||
Reference in New Issue
Block a user