-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial Waitlisting Feature Dev (#506)
* added models and serializers for waitlisted student * worked on api and testing * added list of waitlisted students * tested waitlist code, refactored for sections, changed to delete, added functionality for when students drop will auto call waitlist * convert viewset, remove add from waitlist from views (#498) * adds file for tests, updates model logic, and removes some model logic from view * updated waitlist * waitlist small changes * completed merge and small fixes and finished testing * deleted json * reverted docker to maain --------- Co-authored-by: Isabella Alpert <[email protected]> Co-authored-by: Faith Dennis <[email protected]> Co-authored-by: Faith Dennis <[email protected]>
1 parent
21563c3
commit ed65782
Showing
12 changed files
with
474 additions
and
105 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
65 changes: 65 additions & 0 deletions
65
csm_web/scheduler/migrations/0033_section_waitlist_capacity_waitlistedstudent.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
# Generated by Django 4.2.7 on 2024-09-24 01:18 | ||
|
||
import django.db.models.deletion | ||
from django.conf import settings | ||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
dependencies = [ | ||
("scheduler", "0032_word_of_the_day"), | ||
] | ||
|
||
operations = [ | ||
migrations.AddField( | ||
model_name="section", | ||
name="waitlist_capacity", | ||
field=models.PositiveSmallIntegerField(default=3), | ||
), | ||
migrations.CreateModel( | ||
name="WaitlistedStudent", | ||
fields=[ | ||
( | ||
"id", | ||
models.AutoField( | ||
auto_created=True, | ||
primary_key=True, | ||
serialize=False, | ||
verbose_name="ID", | ||
), | ||
), | ||
( | ||
"active", | ||
models.BooleanField( | ||
default=True, | ||
help_text="An inactive student is a dropped student.", | ||
), | ||
), | ||
("timestamp", models.DateTimeField(auto_now_add=True)), | ||
( | ||
"course", | ||
models.ForeignKey( | ||
on_delete=django.db.models.deletion.CASCADE, | ||
to="scheduler.course", | ||
), | ||
), | ||
( | ||
"section", | ||
models.ForeignKey( | ||
on_delete=django.db.models.deletion.CASCADE, | ||
to="scheduler.section", | ||
), | ||
), | ||
( | ||
"user", | ||
models.ForeignKey( | ||
on_delete=django.db.models.deletion.CASCADE, | ||
to=settings.AUTH_USER_MODEL, | ||
), | ||
), | ||
], | ||
options={ | ||
"abstract": False, | ||
}, | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# Generated by Django 4.2.7 on 2024-09-24 08:32 | ||
|
||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
dependencies = [ | ||
("scheduler", "0033_section_waitlist_capacity_waitlistedstudent"), | ||
] | ||
|
||
operations = [ | ||
migrations.AddField( | ||
model_name="course", | ||
name="max_waitlist", | ||
field=models.SmallIntegerField(default=3), | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# import pytest | ||
# from django.core.exceptions import ValidationError | ||
# from scheduler.factories import ( | ||
# CourseFactory, | ||
# MentorFactory, | ||
# SectionFactory, | ||
# StudentFactory, | ||
# UserFactory, | ||
# ) | ||
# from scheduler.models import Student, User, WaitlistedStudent | ||
|
||
# @pytest.mark.django_db | ||
# def test_add_waitlist(): | ||
# mentor_user, student_user, waitlist_user = UserFactory.create_batch(3) | ||
# course = CourseFactory.create() | ||
# mentor = MentorFactory.create(course=course, user=mentor_user) | ||
# section = SectionFactory.create(mentor=mentor, capacity=1, waitlist_capacity=1) | ||
# student = Student.objects.create(user=student_user, course=course, section=section) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
from rest_framework import status | ||
from rest_framework.decorators import api_view | ||
from rest_framework.exceptions import PermissionDenied | ||
from rest_framework.response import Response | ||
|
||
from ..models import Section, WaitlistedStudent | ||
from .section import add_student | ||
from .utils import logger | ||
|
||
|
||
@api_view(["POST"]) | ||
def add(request, pk=None): | ||
""" | ||
Endpoint: /api/waitlist/<pk>/add | ||
POST: Add a new waitlist student to section. Pass in section id. Called by user | ||
- if user cannot enroll in section, deny permission | ||
- if user is already on waitlist for this section, deny | ||
- if waitlist is full, deny permission | ||
- if section is not full, enroll instead. | ||
""" | ||
section = Section.objects.get(pk=pk) | ||
course = section.mentor.course | ||
user = request.user | ||
|
||
# Checks that student is able to enroll in the course | ||
if not user.can_enroll_in_course(course): | ||
log_enroll_result( | ||
False, | ||
user, | ||
section, | ||
reason=( | ||
"User already involved in this course or course is closed for" | ||
" enrollment" | ||
), | ||
) | ||
raise PermissionDenied( | ||
"You are either mentoring for this course, already enrolled in a section, " | ||
"or the course is closed for enrollment.", | ||
code=status.HTTP_422_UNPROCESSABLE_ENTITY, | ||
) | ||
|
||
# If there is space in the section, attempt to enroll the student directly | ||
if not section.is_section_full: | ||
return add_student(section, user) | ||
|
||
# If the waitlist is full, throw an error | ||
if section.is_waitlist_full: | ||
log_enroll_result(False, user, section, reason="Waitlist is full") | ||
raise PermissionDenied( | ||
"There is no space available in this section.", code=status.HTTP_423_LOCKED | ||
) | ||
|
||
# Check if the student is already enrolled in the waitlist for this section | ||
waitlist_queryset = WaitlistedStudent.objects.filter( | ||
active=True, section=section, user=user | ||
) | ||
if waitlist_queryset.count() != 0: | ||
log_enroll_result( | ||
False, | ||
user, | ||
section, | ||
reason="User is already waitlisted in this section", | ||
) | ||
raise PermissionDenied( | ||
"You are either already waitlisted in this section.", | ||
code=status.HTTP_423_LOCKED, | ||
) | ||
|
||
# Create the new waitlist student and save | ||
waitlisted_student = WaitlistedStudent.objects.create( | ||
user=user, section=section, course=course | ||
) | ||
waitlisted_student.save() | ||
|
||
log_enroll_result(True, request.user, section) | ||
return Response(status=status.HTTP_201_CREATED) | ||
|
||
|
||
@api_view(["PATCH"]) | ||
def drop(request, pk=None): | ||
""" | ||
Endpoint: /api/waitlistedstudent/<pk>/drop | ||
PATCH: Drop a student off the waitlist. Pass in section ID | ||
- sets to inactive | ||
""" | ||
user = request.user | ||
section = Section.objects.get(pk=pk) | ||
waitlisted_student = WaitlistedStudent.objects.filter( | ||
user=user, section=section | ||
).first() | ||
course = waitlisted_student.course | ||
|
||
# Check that the user has permissions to drop this student | ||
is_coordinator = course.is_coordinator(user) | ||
if waitlisted_student.user != user and not is_coordinator: | ||
raise PermissionDenied( | ||
"You do not have permission to drop this student from the waitlist" | ||
) | ||
|
||
# Remove the waitlisted student | ||
waitlisted_student.active = False | ||
# waitlisted_student.delete() | ||
waitlisted_student.save() | ||
|
||
logger.info( | ||
"<Drop> User %s dropped from Waitlist for Section %s", | ||
user, | ||
waitlisted_student.section, | ||
) | ||
return Response(status=status.HTTP_204_NO_CONTENT) | ||
|
||
|
||
def log_enroll_result(success, user, section, reason=None): | ||
"""Logs waitlist success or failure for a user in a section.""" | ||
if success: | ||
logger.info( | ||
"<Waitlist:Success> User %s enrolled into Waitlist for Section %s", | ||
user, | ||
section, | ||
) | ||
else: | ||
logger.warning( | ||
"<Waitlist:Failure> User %s not enroll in Waitlist for Section %s: %s", | ||
user, | ||
section, | ||
reason, | ||
) |