Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BB-9570] Implement Learning Path Enrollment API #11

Open
wants to merge 31 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
cd8c2a0
temp: adds the enrollment api
tecoholic Mar 10, 2025
46d3384
tests: add unit tests to the LearningPathEnrollmentView and fixes rel…
tecoholic Mar 12, 2025
6c2bb1c
fix: update requiremens using python 3.11
tecoholic Mar 12, 2025
4abfe62
chore: fix liting error from ci
tecoholic Mar 12, 2025
dbaa6ea
fix: regenerate the migrations from the lms container
tecoholic Mar 13, 2025
73c5597
feat: adds the FetcheEnrollmentsView with tests
tecoholic Mar 13, 2025
e996e1d
fix: adds pii annotation to LearningPathEnrollmentAllowed
tecoholic Mar 13, 2025
3c0fd69
feat: adds bulk enrollment API with tests
tecoholic Mar 13, 2025
67103ca
refactor: simplify the LearningPathEnrollmentView
tecoholic Mar 19, 2025
4a9c58e
refactor: migrate the ListEnrollmentsView to generic ListAPIView
tecoholic Mar 19, 2025
1047afb
refactor: remove "id" from the enrollment serializer
tecoholic Mar 19, 2025
ec55f07
fix: names, linting, regenrated migrations
tecoholic Mar 19, 2025
963b631
fix: override the django-simple-history in the constraints
tecoholic Mar 19, 2025
2dce1fa
fix: update pii annotaion
tecoholic Mar 19, 2025
1d6c8f7
fix: update pii docs
tecoholic Mar 19, 2025
5ba8a6e
fix: set self un-enrollment flag to False by default in test_settings
tecoholic Mar 19, 2025
d0e7659
fix: the url patterns and doc strings based on api-docs
tecoholic Mar 19, 2025
66ef733
fix: URLs in the ADR
tecoholic Mar 19, 2025
37b80ed
fix: count enrollment-allowed objects created correctly
tecoholic Mar 19, 2025
0768f3b
test: add PII annotations for historical model
Agrendalath Mar 26, 2025
192a3cb
fix: regenerate the migrations file
tecoholic Mar 25, 2025
fea0362
fix: update all the API URLs in the test_views file
tecoholic Mar 25, 2025
40689df
feat: implement signal handler to process pending enrollments
tecoholic Mar 25, 2025
d549b21
fix: regnerate the migrations with "auto_now_add"
tecoholic Mar 28, 2025
52d534b
fix: db migration issues identified in the CI
tecoholic Mar 28, 2025
cb9188e
fix(docs): issues with the ADR tables
tecoholic Mar 28, 2025
d53c34e
fix(docs): issues with the ADR tables
tecoholic Mar 28, 2025
2223d36
fix: linting issues with the docs
tecoholic Mar 28, 2025
98d1d88
refactor: remove is_superuser check from filter
tecoholic Mar 28, 2025
a546d06
refactor: bulk enroll created "allowed" objects only for non-existing…
tecoholic Mar 28, 2025
103dcb5
test: adds missing tests cases for "re-enrollments"
tecoholic Mar 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .annotation_safe_list.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
# fake_app_2.FakeModel2:
# ".. choice_annotation:": foo, bar, baz

learning_paths.HistoricalLearningPathEnrollment:
".. pii": "The email field is not retired to allow future learners to enroll."
".. pii_types": "email_address"
".. pii_retirement": "retained"

admin.LogEntry:
".. no_pii:": "This model has no PII"
auth.Group:
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,5 @@ docs/learning_paths.*.rst
# Private requirements
requirements/private.in
requirements/private.txt
.idea
default.db
31 changes: 16 additions & 15 deletions docs/decisions/0003-learning-path-enrollment-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ necessary timestamps.

.. _CourseEnrollmentAllowed: https://github.com/openedx/edx-platform/blob/925716415c7794d3447acf575be241d767f5e07c/common/djangoapps/student/models/course_enrollment.py#L1588

.. _4-enroll-api:

4. Enroll API
=============
Expand All @@ -98,7 +99,7 @@ Implement an API exposing the LearningPathEnrollment model. This API will allow
learners to be enrolled in the Learning Path.

+---------------------+-------------------------------------------------------+
| API Path | /api/v1/learning-path-enrollment/<learning_path_id> |
| API Path | /api/v1/learning-paths/<learning_path_id>/enrollment/ |
+---------------------+-------------------------------------------------------+
| Methods | GET, POST, DELETE |
+---------------------+-------------------------------------------------------+
Expand Down Expand Up @@ -180,27 +181,27 @@ This API would list all the Learning Path enrollments
* of the user making the request, for a non-staff user
* of all users, for a staff user

+---------------------+-------------------------------------------------------+
| API Path | /api/v1/enrollment/ |
+---------------------+-------------------------------------------------------+
| Methods | GET |
+---------------------+-------------------------------------------------------+
| Permissions Required| LoggedIn |
+---------------------+-------------------------------------------------------+
+---------------------+----------------------------------------------+
| API Path | /api/v1/learning-paths/enrollments/ |
+---------------------+----------------------------------------------+
| Methods | GET |
+---------------------+----------------------------------------------+
| Permissions Required| LoggedIn |
+---------------------+----------------------------------------------+

6. Bulk enrollment API
======================

In order for staff to bulk enroll users into learning paths, implement the
following API.

+---------------------+-------------------------------------------------------+
| API Path | /api/v1/enrollment/bulk_enroll/ |
+---------------------+-------------------------------------------------------+
| Methods | POST |
+---------------------+-------------------------------------------------------+
| Permissions Required| Staff or Admin |
+---------------------+-------------------------------------------------------+
+---------------------+-------------------------------------------------+
| API Path | /api/v1/learning-paths/enrollments/bulk-enroll/ |
+---------------------+-------------------------------------------------+
| Methods | POST |
+---------------------+-------------------------------------------------+
| Permissions Required| Staff or Admin |
+---------------------+-------------------------------------------------+

The API will accept the following JSON data.

Expand Down
2 changes: 1 addition & 1 deletion learning_paths/api/urls.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
""" API URLs. """
"""API URLs."""

from django.urls import include, path

Expand Down
16 changes: 16 additions & 0 deletions learning_paths/api/v1/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
Django REST framework filters.
"""

from rest_framework.filters import BaseFilterBackend


class AdminOrSelfFilterBackend(BaseFilterBackend):
"""
A filter backend that limits the queryset to the current user for non-staff.
"""

def filter_queryset(self, request, queryset, view):
if request.user.is_staff:
return queryset
return queryset.filter(user=request.user)
27 changes: 27 additions & 0 deletions learning_paths/api/v1/permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""
Django REST framework permissions.
"""

from rest_framework.permissions import BasePermission


class IsAdminOrSelf(BasePermission):
"""
Permission to allow only admins or the user themselves to access the API.

Non-staff users cannot pass "username" that is not their own.
"""

def has_permission(self, request, view):
if request.user.is_staff:
return True

if request.method == "GET":
username = request.query_params.get("username")
else:
username = request.data.get("username")

# For learners, the username passed should match the logged in user
if username:
return request.user.username == username
return True
8 changes: 7 additions & 1 deletion learning_paths/api/v1/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from rest_framework import serializers

from learning_paths.models import LearningPath
from learning_paths.models import LearningPath, LearningPathEnrollment

DEFAULT_STATUS = "active"
IMAGE_WIDTH = 1440
Expand Down Expand Up @@ -85,3 +85,9 @@ class LearningPathGradeSerializer(serializers.Serializer):
learning_path_id = serializers.UUIDField()
grade = serializers.FloatField()
required_grade = serializers.FloatField()


class LearningPathEnrollmentSerializer(serializers.ModelSerializer):
class Meta:
model = LearningPathEnrollment
fields = ("user", "learning_path", "is_active", "enrolled_at")
23 changes: 21 additions & 2 deletions learning_paths/api/v1/tests/factories.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
# pylint: disable=missing-module-docstring,missing-class-docstring
from datetime import datetime, timezone

import factory
from django.contrib import auth
from factory.fuzzy import FuzzyText

from learning_paths.models import LearningPath, LearningPathGradingCriteria
from learning_paths.models import (
LearningPath,
LearningPathEnrollment,
LearningPathGradingCriteria,
)

User = auth.get_user_model()

Expand Down Expand Up @@ -31,7 +37,6 @@ class Meta:
uuid = factory.Faker("uuid4")
display_name = FuzzyText()
slug = FuzzyText()
display_name = FuzzyText()
description = FuzzyText()
sequential = False

Expand All @@ -43,3 +48,17 @@ class Meta:
learning_path = factory.SubFactory(LearnerPathwayFactory)
required_completion = 0.80
required_grade = 0.75


class LearningPathEnrollmentFactory(factory.django.DjangoModelFactory):
"""
Factory for LearningPathEnrollment model.
"""

user = factory.SubFactory(UserFactory)
learning_path = factory.SubFactory(LearnerPathwayFactory)
is_active = True
enrolled_at = factory.LazyFunction(lambda: datetime.now(timezone.utc))

class Meta:
model = LearningPathEnrollment
Loading