diff --git a/backend/app/models.py b/backend/app/models.py index 1cfcae3..d2eecee 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -77,6 +77,7 @@ class Course(db.Model): instructor = sa.Column(sa.ForeignKey(User.id), index=True) created_at = sa.Column(sa.DateTime) assignments = db.relationship("Assignment", backref="course", lazy="dynamic") + content = db.relationship("Content", backref="course", lazy="dynamic") def __repr__(self) -> str: return f"" @@ -107,7 +108,7 @@ class Assignment(db.Model): created_at = sa.Column(sa.DateTime) def from_dict(self, data) -> None: - for field in ["name", "course_id", "description", "due_date"]: + for field in ["name", "course_id", "description", "due_date"]: if field in data: setattr(self, field, data[field]) @@ -116,7 +117,30 @@ class Assignment(db.Model): def to_dict(self) -> dict: d = {} - for f in ["id", "name", "course_id", "description", "due_date", "created_at"]: + for f in ["id", "name", "course_id", "description", "due_date", "created_at"]: + d[f] = getattr(self, f) + + return d + + +class Content(db.Model): + id = sa.Column(sa.Integer, primary_key=True) + name = sa.Column(sa.String(128), index=True) + body = sa.Column(sa.Text, index=True) + course_id = sa.Column(sa.ForeignKey(Course.id), index=True) + created_at = sa.Column(sa.DateTime) + + def from_dict(self, data) -> None: + for field in ["name", "body", "course_id"]: + if field in data: + setattr(self, field, data[field]) + + if not self.created_at: + self.created_at = datetime.now() + + def to_dict(self) -> dict: + d = {} + for f in ["id", "course_id", "name", "body", "created_at"]: d[f] = getattr(self, f) return d diff --git a/backend/app/routes.py b/backend/app/routes.py index 9b480f1..00f61c7 100644 --- a/backend/app/routes.py +++ b/backend/app/routes.py @@ -6,7 +6,7 @@ from app.errors import error_response from flask_login import current_user from app import login, db -from app.models import Course, User, Assignment +from app.models import Content, Course, User, Assignment @login.user_loader @@ -31,6 +31,16 @@ def instructor_required(func): return dec +def admin_required(func): + @wraps(func) + def dec(*args, **kwargs): + if current_user.role != "admin": + return error_response(400, "User is not an admin!") + return func(*args, **kwargs) + + return dec + + @bp.route("/login", methods=["POST"]) def login_route(): data = request.get_json() @@ -139,6 +149,21 @@ def get_students_in_course(id): resp["students"].append(s.to_dict()) return jsonify(resp) + +@bp.route("/course//content", methods=["GET"]) +@login_required +def get_content_in_course(id): + c = Course.query.get(id) + if not c: + return error_response(400, f"course with id {id} not found") + + content = c.content.all() + resp = {"content": []} + for c in content: + resp["content"].append(c.to_dict()) + return jsonify(resp) + + @bp.route("/course//assignments", methods=["GET"]) @login_required def get_assignments_in_course(id): @@ -234,6 +259,7 @@ def create_assignment(): db.session.commit() return jsonify(a.to_dict()) + @bp.route("/assignment/", methods=["GET"]) @login_required def get_assignment(id): @@ -243,6 +269,7 @@ def get_assignment(id): return jsonify(a.to_dict()) + @bp.route("/assignment/", methods=["DELETE"]) @login_required @instructor_required @@ -255,6 +282,7 @@ def delete_assignment(id): db.session.commit() return jsonify(a.to_dict()) + @bp.route("/assignment/", methods=["PUT"]) @login_required @instructor_required @@ -273,3 +301,61 @@ def update_assignment(id): db.session.commit() return jsonify(a.to_dict()) + +@bp.route("/content", methods=["POST"]) +@login_required +@instructor_required +def create_content(): + data = request.get_json() + required_fields = ["name", "body", "course_id"] + if f := check_data(data, required_fields): + return error_response(400, f"Must supply {f}") + + c = Content() + c.from_dict(data) + db.session.add(c) + db.session.commit() + + return jsonify(c.to_dict()) + + +@bp.route("/content/", methods=["GET"]) +@login_required +def get_content(id): + c = Content.query.get(id) + if not c: + return error_response(400, f"Content with id {id} does not exist") + + return jsonify(c.to_dict()) + + +@bp.route("/content/", methods=["DELETE"]) +@login_required +@instructor_required +def delete_content(id): + c = Content.query.get(id) + if not c: + return error_response(400, f"Content with id {id} does not exist") + + db.session.delete(c) + db.session.commit() + return jsonify(c.to_dict()) + + +@bp.route("/content/", methods=["PUT"]) +@login_required +@instructor_required +def update_content(id): + c = Content.query.get(id) + if not c: + return error_response(400, f"Content with id {id} does not exist") + + data = request.get_json() + expected_fields = ["name", "body"] + for d in data: + if d not in expected_fields: + return error_response(400, f"Field {d} was not expected") + + c.from_dict(data) + db.session.commit() + return jsonify(c.to_dict()) diff --git a/backend/migrations/versions/c62aec1c6b91_create_content_model.py b/backend/migrations/versions/c62aec1c6b91_create_content_model.py new file mode 100644 index 0000000..aa81aad --- /dev/null +++ b/backend/migrations/versions/c62aec1c6b91_create_content_model.py @@ -0,0 +1,46 @@ +"""Create content model + +Revision ID: c62aec1c6b91 +Revises: 1e88e783d238 +Create Date: 2023-04-14 16:46:43.513842 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'c62aec1c6b91' +down_revision = '1e88e783d238' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('content', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=128), nullable=True), + sa.Column('body', sa.Text(), nullable=True), + sa.Column('course_id', sa.Integer(), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=True), + sa.ForeignKeyConstraint(['course_id'], ['course.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('content', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_content_body'), ['body'], unique=False) + batch_op.create_index(batch_op.f('ix_content_course_id'), ['course_id'], unique=False) + batch_op.create_index(batch_op.f('ix_content_name'), ['name'], unique=False) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('content', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_content_name')) + batch_op.drop_index(batch_op.f('ix_content_course_id')) + batch_op.drop_index(batch_op.f('ix_content_body')) + + op.drop_table('content') + # ### end Alembic commands ###