Conversation
…d drop), and auto add to section when student is dropped
… sections db on admin site
… sections db on admin site
* update sections * added a count waitlist method * displaying active feature for waitlisted database in admin site * showing only active students when we click on particular section from sections db on admin site * add waitlistCapacity to MetaEditModal and MentorSectionInfo --------- Co-authored-by: Eileen Hwang <eileenhwang@berkeley.edu> Co-authored-by: rajoshi <rajoshibasu@gmail.com>
…m_web into feat/waitlisting/frontend
Co-authored-by: maxmwang <orangemax888@gmail.com>
…t race conditions
There was a problem hiding this comment.
Pull request overview
This pull request implements a comprehensive waitlist feature for course sections, allowing students to join waitlists when sections are full and be automatically enrolled when spots open up. The feature includes backend models, API endpoints, frontend UI components, and extensive test coverage.
Changes:
- Adds WaitlistedStudent model with position-based queuing and automatic enrollment from waitlist when spots open
- Implements waitlist API endpoints for adding, dropping, viewing waitlisted students and checking position
- Updates frontend to display waitlist information, allow students to join waitlists, and show waitlist positions
- Adds comprehensive test suite with 20+ test cases covering various waitlist scenarios
Reviewed changes
Copilot reviewed 27 out of 28 changed files in this pull request and generated 21 comments.
Show a summary per file
| File | Description |
|---|---|
| csm_web/scheduler/models.py | Adds WaitlistedStudent model, waitlist capacity fields, and user permission methods for waitlisting |
| csm_web/scheduler/views/waitlistedStudent.py | New file with API endpoints for waitlist operations (view, add, drop, position) |
| csm_web/scheduler/views/section.py | Refactors enrollment logic into helper functions, adds waitlist promotion on drop |
| csm_web/scheduler/views/student.py | Updates student drop to trigger waitlist promotion, improves logging format |
| csm_web/scheduler/serializers.py | Adds WaitlistedStudent serializer and waitlist fields to section serializer |
| csm_web/scheduler/urls.py | Registers waitlist API endpoints |
| csm_web/scheduler/admin.py | Adds admin interface for WaitlistedStudent model |
| csm_web/scheduler/factories.py | Adds factory for generating test waitlist data |
| csm_web/scheduler/tests/models/test_waitlisted_student.py | Comprehensive test suite with 880 lines covering waitlist functionality |
| csm_web/scheduler/migrations/0035_*.py | Database migration adding waitlist tables and fields |
| csm_web/frontend/src/components/section/* | UI components for displaying and managing waitlists |
| csm_web/frontend/src/utils/queries/sections.tsx | React Query hooks for waitlist API operations |
| csm_web/frontend/src/utils/types.tsx | TypeScript type definitions for waitlist role and section fields |
| csm_web/frontend/src/components/course/SectionCard.tsx | Updates enrollment UI to show waitlist capacity and handle waitlist joins |
| .babelrc.json | Removes lodash plugin from Babel configuration |
| csm_web/pytest.ini | Adds --reuse-db and --no-cacheprovider options |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| student.save() | ||
| logger.info( | ||
| f"<Drop> User {log_str(request.user)} dropped Section {log_str(student.section)} for Student user {log_str(student.user)}" | ||
| "<Drop> User %s dropped Section %sfor Student user %s", |
There was a problem hiding this comment.
Missing space in log message. Should be "Section %s for Student user %s" (add space after %s).
| "<Drop> User %s dropped Section %sfor Student user %s", | |
| "<Drop> User %s dropped Section %s for Student user %s", |
| enrollment_end = models.DateTimeField() | ||
| permitted_absences = models.PositiveSmallIntegerField() | ||
| # time limit for wotd submission; | ||
| # time limit fdocor wotd submission; |
There was a problem hiding this comment.
Typo in comment. "fdocor" should be "for".
| # time limit fdocor wotd submission; | |
| # time limit for wotd submission; |
| @api_view(["GET"]) | ||
| def view(request, pk=None): | ||
| """ | ||
| Endpoint: /api/waitlist/<pk> | ||
| pk = section id | ||
|
|
||
| GET: View all students on the waitlist for a section | ||
| """ | ||
| section = get_object_or_error(Section.objects, pk=pk) | ||
| if section.mentor is None: | ||
| raise NotFound("This section has no mentor assigned.") | ||
| is_mentor = request.user == section.mentor.user | ||
| is_coord = bool( | ||
| section.mentor.course.coordinator_set.filter(user=request.user).count() | ||
| ) | ||
| if not is_mentor and not is_coord: | ||
| raise PermissionDenied("You do not have permission to view this waitlist") | ||
|
|
||
| waitlist_queryset = WaitlistedStudent.objects.filter(active=True, section=section) | ||
| return Response(WaitlistedStudentSerializer(waitlist_queryset, many=True).data) |
There was a problem hiding this comment.
Missing authentication check. The view, count_waitlist, and position endpoints don't verify that the user is authenticated. While Django REST Framework may handle this at a higher level, it's best practice to be explicit about authentication requirements, especially for sensitive endpoints that return student waitlist information. Consider adding @permission_classes([IsAuthenticated]) or similar decorator to ensure only authenticated users can access these endpoints.
| @property | ||
| def current_student_count(self): | ||
| """Query the number of students currently enrolled in this section.""" | ||
| return self.students.filter(active=True).count() |
There was a problem hiding this comment.
The @property decorator is used here instead of @cached_property. The original code used @functional.cached_property for current_student_count. Since this property now makes a database query every time it's accessed, this could lead to N+1 query problems if the property is accessed multiple times. Consider using @cached_property from functools (Python 3.8+) or Django's equivalent to cache the result within a single request lifecycle.
| section = get_object_or_error(Section.objects, pk=pk) | ||
| section = Section.objects.select_for_update().get(pk=section.pk) |
There was a problem hiding this comment.
The section queried at line 51 is then re-queried with select_for_update at line 52, which is redundant. The first query with get_object_or_error is unnecessary since you immediately re-fetch it. Consider using Section.objects.select_for_update().get(pk=pk) directly within the transaction and handling the DoesNotExist exception appropriately (or using get_object_or_error with a queryset that already has select_for_update applied).
| section = get_object_or_error(Section.objects, pk=pk) | |
| section = Section.objects.select_for_update().get(pk=section.pk) | |
| section = get_object_or_error(Section.objects.select_for_update(), pk=pk) |
| for waitlisted_student in waitlisted_students: | ||
| try: | ||
| cascade_section_id = swap_into_section( | ||
| waitlisted_student.section, waitlisted_student.user | ||
| ) | ||
| except PermissionDenied: | ||
| continue | ||
|
|
||
| logger.info( | ||
| "<Enrollment:Success> User %s removed from all Waitlists for Course %s", | ||
| log_str(waitlisted_student.user), | ||
| log_str(waitlisted_student.course), | ||
| ) | ||
| response = Response(status=status.HTTP_201_CREATED) | ||
| break |
There was a problem hiding this comment.
The waitlisted student is not being deactivated after successful enrollment from waitlist. The function documentation at line 198 states "Waitlist student is deactivated" but the code doesn't set waitlisted_student.active = False after successfully adding the student via swap_into_section. The student removal from waitlists happens inside the add_student function, but that only handles the enrolled user's waitlists, not the specific waitlisted_student record being processed here. This needs to be explicitly deactivated.
| return Response(status=status.HTTP_204_NO_CONTENT) | ||
|
|
||
|
|
||
| def log_enroll_result(success, user, section, reason=None): |
There was a problem hiding this comment.
The helper function name log_enroll_result is misleading in the context of waitlisting. The function is logging waitlist enrollment, not section enrollment. Consider renaming to log_waitlist_result for clarity.
| { | ||
| "presets": ["@babel/preset-react", "@babel/preset-typescript"], | ||
| "plugins": ["transform-class-properties", "lodash", "@babel/plugin-transform-runtime", "dynamic-import-node"] | ||
| "plugins": ["transform-class-properties", "@babel/plugin-transform-runtime", "dynamic-import-node"] |
There was a problem hiding this comment.
The lodash plugin was removed from the Babel configuration. If lodash is used anywhere in the frontend code, this could lead to larger bundle sizes as tree-shaking won't be as effective. Verify that lodash is not used in the codebase, or if it is, ensure that imports are done correctly (e.g., import map from 'lodash/map' instead of import { map } from 'lodash') to enable tree-shaking.
| "plugins": ["transform-class-properties", "@babel/plugin-transform-runtime", "dynamic-import-node"] | |
| "plugins": ["transform-class-properties", "@babel/plugin-transform-runtime", "lodash", "dynamic-import-node"] |
| [pytest] | ||
| DJANGO_SETTINGS_MODULE = csm_web.settings | ||
| python_files = tests.py test_*.py *_tests.py | ||
| addopts = --reuse-db -p no:cacheprovider |
There was a problem hiding this comment.
The pytest.ini configuration adds --reuse-db which can cause issues in CI/CD environments or when running tests in parallel. This flag reuses the test database between runs, which can lead to test pollution if not handled carefully. Consider making this an optional flag rather than a default option, especially if tests are run in CI where a clean database state is typically expected.
| addopts = --reuse-db -p no:cacheprovider | |
| addopts = -p no:cacheprovider |
|
|
||
| for waitlist in waitlist_set: | ||
| waitlist.active = False | ||
| # waitlist.delete() |
There was a problem hiding this comment.
The commented-out code for deleting waitlisted students (lines 134, 227) should either be removed or have a comment explaining why it's kept. Commented-out code can be confusing for future maintainers and clutters the codebase. If there's a reason to keep the delete approach as a reference, add a comment explaining the decision to use active=False instead of deletion.
| # waitlist.delete() |
No description provided.