From 8ec489beb325d1b3e614806a8384c2a01ecf434b Mon Sep 17 00:00:00 2001 From: Jagraj Aulakh Date: Thu, 6 Apr 2023 18:51:43 -0400 Subject: [PATCH 1/5] #6 Created route to enroll student into a course --- backend/app/models.py | 1 + backend/app/routes.py | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/backend/app/models.py b/backend/app/models.py index 3431fc8..4a4044d 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -42,6 +42,7 @@ class User(UserMixin, db.Model): def enroll(self, c) -> bool: if not self.is_enrolled(c): self.enrolled_courses.append(c) + db.session.commit() return True return False diff --git a/backend/app/routes.py b/backend/app/routes.py index fd86958..75e7c57 100644 --- a/backend/app/routes.py +++ b/backend/app/routes.py @@ -73,6 +73,7 @@ def register(): @bp.route("/course", methods=["POST"]) +@login_required def create_course(): data = request.get_json() @@ -97,6 +98,7 @@ def create_course(): @bp.route("/user//courses", methods=["GET"]) +@login_required def get_courses(id): u = User.query.get(id) d = {"courses": []} @@ -104,3 +106,21 @@ def get_courses(id): d["courses"].append(c.to_dict()) resp = jsonify(d) return resp + + +@bp.route("/user//enroll/", methods=["POST"]) +@login_required +def enroll_student(uid, cid): + u = User.query.get(uid) + if not u: + return error_response(400, f"User with id {uid} does not exist") + + c = Course.query.get(cid) + if not c: + return error_response(400, f"Course with id {cid} does not exist") + + if not u.enroll(c): + return error_response(400, f"User {uid} is already enrolled in course {cid}") + + resp = {"user": u.to_dict(), "course": c.to_dict()} + return jsonify(resp) -- 2.49.1 From b71c23d70687ed9d1e4243379b01311faa586eaf Mon Sep 17 00:00:00 2001 From: Jagraj Aulakh Date: Thu, 6 Apr 2023 19:06:44 -0400 Subject: [PATCH 2/5] #6 Added unenroll endpoint by sending DELETE request to enroll endpoint --- backend/app/models.py | 7 +++++++ backend/app/routes.py | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/backend/app/models.py b/backend/app/models.py index 4a4044d..2ca99b8 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -46,6 +46,13 @@ class User(UserMixin, db.Model): return True return False + def unenroll(self, c) -> bool: + if self.is_enrolled(c): + self.enrolled_courses.remove(c) + db.session.commit() + return True + return False + def to_dict(self) -> dict: return { "id": self.id, diff --git a/backend/app/routes.py b/backend/app/routes.py index 75e7c57..00968a1 100644 --- a/backend/app/routes.py +++ b/backend/app/routes.py @@ -124,3 +124,20 @@ def enroll_student(uid, cid): resp = {"user": u.to_dict(), "course": c.to_dict()} return jsonify(resp) + + +@bp.route("/user//enroll/", methods=["DELETE"]) +@login_required +def unenroll_student(uid, cid): + u = User.query.get(uid) + if not u: + return error_response(400, f"User with id {uid} does not exist") + + c = Course.query.get(cid) + if not c: + return error_response(400, f"Course with id {cid} does not exist") + + if not u.unenroll(c): + return error_response(400, f"User {uid} is not enrolled in course {cid}") + resp = {"user": u.to_dict(), "course": c.to_dict()} + return jsonify(resp) -- 2.49.1 From a789c2eb83830a9bde6b9a31712d392d1c04b674 Mon Sep 17 00:00:00 2001 From: Jagraj Aulakh Date: Thu, 6 Apr 2023 21:25:34 -0400 Subject: [PATCH 3/5] #6 Refactored both functionalities to one function that accepts both request methods --- backend/app/routes.py | 27 ++++++++------------------- frontend/public/index.html | 2 +- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/backend/app/routes.py b/backend/app/routes.py index 00968a1..f33a43c 100644 --- a/backend/app/routes.py +++ b/backend/app/routes.py @@ -108,7 +108,7 @@ def get_courses(id): return resp -@bp.route("/user//enroll/", methods=["POST"]) +@bp.route("/user//enroll/", methods=["POST", "DELETE"]) @login_required def enroll_student(uid, cid): u = User.query.get(uid) @@ -119,25 +119,14 @@ def enroll_student(uid, cid): if not c: return error_response(400, f"Course with id {cid} does not exist") - if not u.enroll(c): - return error_response(400, f"User {uid} is already enrolled in course {cid}") + if request.method == "POST": + if not u.enroll(c): + return error_response(400, f"User {uid} is already enrolled in course {cid}") + + elif request.method == "DELETE": + if not u.unenroll(c): + return error_response(400, f"User {uid} is not enrolled in course {cid}") resp = {"user": u.to_dict(), "course": c.to_dict()} return jsonify(resp) - -@bp.route("/user//enroll/", methods=["DELETE"]) -@login_required -def unenroll_student(uid, cid): - u = User.query.get(uid) - if not u: - return error_response(400, f"User with id {uid} does not exist") - - c = Course.query.get(cid) - if not c: - return error_response(400, f"Course with id {cid} does not exist") - - if not u.unenroll(c): - return error_response(400, f"User {uid} is not enrolled in course {cid}") - resp = {"user": u.to_dict(), "course": c.to_dict()} - return jsonify(resp) diff --git a/frontend/public/index.html b/frontend/public/index.html index 104b21e..29efbd7 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -22,7 +22,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - React App + LearningTree -- 2.49.1 From dcb911128858ba382be7f66239c9fbf21ad8f928 Mon Sep 17 00:00:00 2001 From: Jagraj Aulakh Date: Thu, 6 Apr 2023 21:49:09 -0400 Subject: [PATCH 4/5] #6 Add mysql client requirement to requirements --- backend/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/requirements.txt b/backend/requirements.txt index 6937fee..d11b006 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -12,6 +12,7 @@ itsdangerous==2.1.2 Jinja2==3.1.2 Mako==1.2.4 MarkupSafe==2.1.2 +mysqlclient==2.1.1 python-dotenv==1.0.0 six==1.16.0 SQLAlchemy==2.0.6 -- 2.49.1 From d5d2d830ccf0e2634f21d664016616e1da825013 Mon Sep 17 00:00:00 2001 From: Jagraj Aulakh Date: Thu, 6 Apr 2023 22:00:11 -0400 Subject: [PATCH 5/5] #35 Added course code field to Course model. Updated class functions to reflect change. Also added duplicate checking for create course endpoint --- backend/app/models.py | 7 ++-- backend/app/routes.py | 6 +++- .../versions/862905f5e34a_add_course_code.py | 34 +++++++++++++++++++ 3 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 backend/migrations/versions/862905f5e34a_add_course_code.py diff --git a/backend/app/models.py b/backend/app/models.py index 2ca99b8..d91c295 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -71,15 +71,16 @@ class User(UserMixin, db.Model): class Course(db.Model): id = sa.Column(sa.Integer, primary_key=True) name = sa.Column(sa.String(128), index=True) + course_code = sa.Column(sa.String(32), index=True) description = sa.Column(sa.Text, index=True) instructor = sa.Column(sa.ForeignKey(User.id), index=True) created_at = sa.Column(sa.DateTime) def __repr__(self) -> str: - return f"" + return f"" def from_dict(self, data) -> None: - for field in ["name", "description", "instructor"]: + for field in ["name", "course_code", "description", "instructor"]: if field in data: setattr(self, field, data[field]) @@ -88,7 +89,7 @@ class Course(db.Model): def to_dict(self) -> dict: d = {} - for f in ["id", "name", "description", "created_at"]: + for f in ["id", "name", "course_code", "description", "created_at"]: d[f] = getattr(self, f) d["instructor"] = User.query.get(self.instructor).username diff --git a/backend/app/routes.py b/backend/app/routes.py index f33a43c..89dfa1e 100644 --- a/backend/app/routes.py +++ b/backend/app/routes.py @@ -77,7 +77,7 @@ def register(): def create_course(): data = request.get_json() - required_fields = ["name", "description", "instructor"] + required_fields = ["name", "course_code", "description", "instructor"] if f := check_data(data, required_fields): return error_response(400, f"Must supply {f}") @@ -86,6 +86,10 @@ def create_course(): if not u: return error_response(400, f"User with id {data['instructor']} does not exist") + 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") diff --git a/backend/migrations/versions/862905f5e34a_add_course_code.py b/backend/migrations/versions/862905f5e34a_add_course_code.py new file mode 100644 index 0000000..d408a8b --- /dev/null +++ b/backend/migrations/versions/862905f5e34a_add_course_code.py @@ -0,0 +1,34 @@ +"""Add course code + +Revision ID: 862905f5e34a +Revises: 093a66f0b581 +Create Date: 2023-04-06 21:55:54.838647 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '862905f5e34a' +down_revision = '093a66f0b581' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('course', schema=None) as batch_op: + batch_op.add_column(sa.Column('course_code', sa.String(length=32), nullable=True)) + batch_op.create_index(batch_op.f('ix_course_course_code'), ['course_code'], unique=False) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('course', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_course_course_code')) + batch_op.drop_column('course_code') + + # ### end Alembic commands ### -- 2.49.1