From 80ccc17eef7376350d304ad54f902f45eab71469 Mon Sep 17 00:00:00 2001 From: Jagraj Aulakh Date: Fri, 14 Apr 2023 16:47:27 -0400 Subject: [PATCH 1/6] #70 Add content model --- backend/app/models.py | 27 ++++++++++- .../c62aec1c6b91_create_content_model.py | 46 +++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 backend/migrations/versions/c62aec1c6b91_create_content_model.py diff --git a/backend/app/models.py b/backend/app/models.py index 1cfcae3..7676018 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -107,7 +107,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 +116,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"]: + 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/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 ### -- 2.49.1 From 245b7bbb2a5e738172ff087313d62e28447048e6 Mon Sep 17 00:00:00 2001 From: Jagraj Aulakh Date: Fri, 14 Apr 2023 16:54:34 -0400 Subject: [PATCH 2/6] #70 Add endpoint to create content --- backend/app/models.py | 3 ++- backend/app/routes.py | 32 +++++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/backend/app/models.py b/backend/app/models.py index 7676018..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"" @@ -130,7 +131,7 @@ class Content(db.Model): created_at = sa.Column(sa.DateTime) def from_dict(self, data) -> None: - for field in ["name", "body"]: + for field in ["name", "body", "course_id"]: if field in data: setattr(self, field, data[field]) diff --git a/backend/app/routes.py b/backend/app/routes.py index 9b480f1..0fb8692 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,7 @@ def get_students_in_course(id): resp["students"].append(s.to_dict()) return jsonify(resp) + @bp.route("/course//assignments", methods=["GET"]) @login_required def get_assignments_in_course(id): @@ -234,6 +245,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 +255,7 @@ def get_assignment(id): return jsonify(a.to_dict()) + @bp.route("/assignment/", methods=["DELETE"]) @login_required @instructor_required @@ -255,6 +268,7 @@ def delete_assignment(id): db.session.commit() return jsonify(a.to_dict()) + @bp.route("/assignment/", methods=["PUT"]) @login_required @instructor_required @@ -273,3 +287,19 @@ 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()) -- 2.49.1 From 608854ebb02b1f9150fcfd1b2f13142a5a10d86c Mon Sep 17 00:00:00 2001 From: Jagraj Aulakh Date: Fri, 14 Apr 2023 16:56:25 -0400 Subject: [PATCH 3/6] #70 Add endpoint to get all content in a specific course --- backend/app/routes.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/backend/app/routes.py b/backend/app/routes.py index 0fb8692..df606d3 100644 --- a/backend/app/routes.py +++ b/backend/app/routes.py @@ -149,6 +149,18 @@ 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 -- 2.49.1 From 0ea4e3933fbb2bdd85429747b09a86478aa46ffb Mon Sep 17 00:00:00 2001 From: Jagraj Aulakh Date: Fri, 14 Apr 2023 16:57:58 -0400 Subject: [PATCH 4/6] #70 Add endpoint to get content by id --- backend/app/routes.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/backend/app/routes.py b/backend/app/routes.py index df606d3..d2c2166 100644 --- a/backend/app/routes.py +++ b/backend/app/routes.py @@ -149,6 +149,7 @@ 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): @@ -162,6 +163,7 @@ def get_content_in_course(id): resp["content"].append(c.to_dict()) return jsonify(resp) + @bp.route("/course//assignments", methods=["GET"]) @login_required def get_assignments_in_course(id): @@ -315,3 +317,13 @@ def create_content(): 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()) -- 2.49.1 From cab68b147c90caf43f0827ee223db9034dc61ed8 Mon Sep 17 00:00:00 2001 From: Jagraj Aulakh Date: Fri, 14 Apr 2023 17:00:10 -0400 Subject: [PATCH 5/6] #70 Add endpoint to delete content --- backend/app/routes.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/backend/app/routes.py b/backend/app/routes.py index d2c2166..f70aab3 100644 --- a/backend/app/routes.py +++ b/backend/app/routes.py @@ -327,3 +327,16 @@ def get_content(id): 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()) -- 2.49.1 From 8626fd9a44af7188ed7dd38ebe6e456da3f50b55 Mon Sep 17 00:00:00 2001 From: Jagraj Aulakh Date: Fri, 14 Apr 2023 17:02:01 -0400 Subject: [PATCH 6/6] #70 Add endpoint to update content --- backend/app/routes.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/backend/app/routes.py b/backend/app/routes.py index f70aab3..00f61c7 100644 --- a/backend/app/routes.py +++ b/backend/app/routes.py @@ -340,3 +340,22 @@ def delete_content(id): 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()) -- 2.49.1