feat: add first batch of Open edX Filters
* Add PreEnrollmentFilter * Add PreRegisterFilter * Add PreLoginFilter
This commit is contained in:
@@ -66,6 +66,7 @@ from openedx_events.learning.signals import (
|
|||||||
COURSE_ENROLLMENT_CREATED,
|
COURSE_ENROLLMENT_CREATED,
|
||||||
COURSE_UNENROLLMENT_COMPLETED,
|
COURSE_UNENROLLMENT_COMPLETED,
|
||||||
)
|
)
|
||||||
|
from openedx_filters.learning.filters import CourseEnrollmentStarted
|
||||||
import openedx.core.djangoapps.django_comment_common.comment_client as cc
|
import openedx.core.djangoapps.django_comment_common.comment_client as cc
|
||||||
from common.djangoapps.course_modes.models import CourseMode, get_cosmetic_verified_display_price
|
from common.djangoapps.course_modes.models import CourseMode, get_cosmetic_verified_display_price
|
||||||
from common.djangoapps.student.emails import send_proctoring_requirements_email
|
from common.djangoapps.student.emails import send_proctoring_requirements_email
|
||||||
@@ -1117,6 +1118,10 @@ class AlreadyEnrolledError(CourseEnrollmentException):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class EnrollmentNotAllowed(CourseEnrollmentException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CourseEnrollmentManager(models.Manager):
|
class CourseEnrollmentManager(models.Manager):
|
||||||
"""
|
"""
|
||||||
Custom manager for CourseEnrollment with Table-level filter methods.
|
Custom manager for CourseEnrollment with Table-level filter methods.
|
||||||
@@ -1627,6 +1632,13 @@ class CourseEnrollment(models.Model):
|
|||||||
|
|
||||||
Also emits relevant events for analytics purposes.
|
Also emits relevant events for analytics purposes.
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
|
user, course_key, mode = CourseEnrollmentStarted.run_filter(
|
||||||
|
user=user, course_key=course_key, mode=mode,
|
||||||
|
)
|
||||||
|
except CourseEnrollmentStarted.PreventEnrollment as exc:
|
||||||
|
raise EnrollmentNotAllowed(str(exc)) from exc
|
||||||
|
|
||||||
if mode is None:
|
if mode is None:
|
||||||
mode = _default_course_mode(str(course_key))
|
mode = _default_course_mode(str(course_key))
|
||||||
# All the server-side checks for whether a user is allowed to enroll.
|
# All the server-side checks for whether a user is allowed to enroll.
|
||||||
|
|||||||
104
common/djangoapps/student/tests/test_filters.py
Normal file
104
common/djangoapps/student/tests/test_filters.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
"""
|
||||||
|
Test that various filters are fired for models in the student app.
|
||||||
|
"""
|
||||||
|
from django.test import override_settings
|
||||||
|
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||||
|
from xmodule.modulestore.tests.factories import CourseFactory
|
||||||
|
from openedx_filters.learning.filters import CourseEnrollmentStarted
|
||||||
|
from openedx_filters import PipelineStep
|
||||||
|
|
||||||
|
from common.djangoapps.student.models import CourseEnrollment, EnrollmentNotAllowed
|
||||||
|
from common.djangoapps.student.tests.factories import UserFactory, UserProfileFactory
|
||||||
|
from openedx.core.djangolib.testing.utils import skip_unless_lms
|
||||||
|
|
||||||
|
|
||||||
|
class TestEnrollmentPipelineStep(PipelineStep):
|
||||||
|
"""
|
||||||
|
Utility function used when getting steps for pipeline.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def run_filter(self, user, course_key, mode): # pylint: disable=arguments-differ
|
||||||
|
"""Pipeline steps that changes mode to honor."""
|
||||||
|
if mode == "no-id-professional":
|
||||||
|
raise CourseEnrollmentStarted.PreventEnrollment()
|
||||||
|
return {"mode": "honor"}
|
||||||
|
|
||||||
|
|
||||||
|
@skip_unless_lms
|
||||||
|
class EnrollmentFiltersTest(ModuleStoreTestCase):
|
||||||
|
"""
|
||||||
|
Tests for the Open edX Filters associated with the enrollment process through the enroll method.
|
||||||
|
|
||||||
|
This class guarantees that the following filters are triggered during the user's enrollment:
|
||||||
|
|
||||||
|
- CourseEnrollmentStarted
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self): # pylint: disable=arguments-differ
|
||||||
|
super().setUp()
|
||||||
|
self.course = CourseFactory.create()
|
||||||
|
self.user = UserFactory.create(
|
||||||
|
username="test",
|
||||||
|
email="test@example.com",
|
||||||
|
password="password",
|
||||||
|
)
|
||||||
|
self.user_profile = UserProfileFactory.create(user=self.user, name="Test Example")
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
OPEN_EDX_FILTERS_CONFIG={
|
||||||
|
"org.openedx.learning.course.enrollment.started.v1": {
|
||||||
|
"pipeline": [
|
||||||
|
"common.djangoapps.student.tests.test_filters.TestEnrollmentPipelineStep",
|
||||||
|
],
|
||||||
|
"fail_silently": False,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def test_enrollment_filter_executed(self):
|
||||||
|
"""
|
||||||
|
Test whether the student enrollment filter is triggered before the user's
|
||||||
|
enrollment process.
|
||||||
|
|
||||||
|
Expected result:
|
||||||
|
- CourseEnrollmentStarted is triggered and executes TestEnrollmentPipelineStep.
|
||||||
|
- The arguments that the receiver gets are the arguments used by the filter
|
||||||
|
with the enrollment mode changed.
|
||||||
|
"""
|
||||||
|
enrollment = CourseEnrollment.enroll(self.user, self.course.id, mode='audit')
|
||||||
|
|
||||||
|
self.assertEqual('honor', enrollment.mode)
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
OPEN_EDX_FILTERS_CONFIG={
|
||||||
|
"org.openedx.learning.course.enrollment.started.v1": {
|
||||||
|
"pipeline": [
|
||||||
|
"common.djangoapps.student.tests.test_filters.TestEnrollmentPipelineStep",
|
||||||
|
],
|
||||||
|
"fail_silently": False,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def test_enrollment_filter_prevent_enroll(self):
|
||||||
|
"""
|
||||||
|
Test prevent the user's enrollment through a pipeline step.
|
||||||
|
|
||||||
|
Expected result:
|
||||||
|
- CourseEnrollmentStarted is triggered and executes TestEnrollmentPipelineStep.
|
||||||
|
- The user can't enroll.
|
||||||
|
"""
|
||||||
|
with self.assertRaises(EnrollmentNotAllowed):
|
||||||
|
CourseEnrollment.enroll(self.user, self.course.id, mode='no-id-professional')
|
||||||
|
|
||||||
|
@override_settings(OPEN_EDX_FILTERS_CONFIG={})
|
||||||
|
def test_enrollment_without_filter_configuration(self):
|
||||||
|
"""
|
||||||
|
Test usual enrollment process, without filter's intervention.
|
||||||
|
|
||||||
|
Expected result:
|
||||||
|
- CourseEnrollmentStarted does not have any effect on the enrollment process.
|
||||||
|
- The enrollment process ends successfully.
|
||||||
|
"""
|
||||||
|
enrollment = CourseEnrollment.enroll(self.user, self.course.id, mode='audit')
|
||||||
|
|
||||||
|
self.assertEqual('audit', enrollment.mode)
|
||||||
|
self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course.id))
|
||||||
@@ -29,6 +29,7 @@ from rest_framework.views import APIView
|
|||||||
|
|
||||||
from openedx_events.learning.data import UserData, UserPersonalData
|
from openedx_events.learning.data import UserData, UserPersonalData
|
||||||
from openedx_events.learning.signals import SESSION_LOGIN_COMPLETED
|
from openedx_events.learning.signals import SESSION_LOGIN_COMPLETED
|
||||||
|
from openedx_filters.learning.filters import StudentLoginRequested
|
||||||
|
|
||||||
from common.djangoapps import third_party_auth
|
from common.djangoapps import third_party_auth
|
||||||
from common.djangoapps.edxmako.shortcuts import render_to_response
|
from common.djangoapps.edxmako.shortcuts import render_to_response
|
||||||
@@ -564,6 +565,13 @@ def login_user(request, api_version='v1'):
|
|||||||
|
|
||||||
possibly_authenticated_user = user
|
possibly_authenticated_user = user
|
||||||
|
|
||||||
|
try:
|
||||||
|
possibly_authenticated_user = StudentLoginRequested.run_filter(user=possibly_authenticated_user)
|
||||||
|
except StudentLoginRequested.PreventLogin as exc:
|
||||||
|
raise AuthFailedError(
|
||||||
|
str(exc), redirect_url=exc.redirect_to, error_code=exc.error_code, context=exc.context,
|
||||||
|
) from exc
|
||||||
|
|
||||||
if not is_user_third_party_authenticated:
|
if not is_user_third_party_authenticated:
|
||||||
possibly_authenticated_user = _authenticate_first_party(request, user, third_party_auth_requested)
|
possibly_authenticated_user = _authenticate_first_party(request, user, third_party_auth_requested)
|
||||||
if possibly_authenticated_user and password_policy_compliance.should_enforce_compliance_on_login():
|
if possibly_authenticated_user and password_policy_compliance.should_enforce_compliance_on_login():
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from edx_django_utils.monitoring import set_custom_attribute
|
|||||||
from edx_toggles.toggles import LegacyWaffleFlag, LegacyWaffleFlagNamespace
|
from edx_toggles.toggles import LegacyWaffleFlag, LegacyWaffleFlagNamespace
|
||||||
from openedx_events.learning.data import UserData, UserPersonalData
|
from openedx_events.learning.data import UserData, UserPersonalData
|
||||||
from openedx_events.learning.signals import STUDENT_REGISTRATION_COMPLETED
|
from openedx_events.learning.signals import STUDENT_REGISTRATION_COMPLETED
|
||||||
|
from openedx_filters.learning.filters import StudentRegistrationRequested
|
||||||
from pytz import UTC
|
from pytz import UTC
|
||||||
from ratelimit.decorators import ratelimit
|
from ratelimit.decorators import ratelimit
|
||||||
from requests import HTTPError
|
from requests import HTTPError
|
||||||
@@ -569,6 +570,14 @@ class RegistrationView(APIView):
|
|||||||
data = request.POST.copy()
|
data = request.POST.copy()
|
||||||
self._handle_terms_of_service(data)
|
self._handle_terms_of_service(data)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = StudentRegistrationRequested.run_filter(form_data=data)
|
||||||
|
except StudentRegistrationRequested.PreventRegistration as exc:
|
||||||
|
errors = {
|
||||||
|
"error_message": [{"user_message": str(exc)}],
|
||||||
|
}
|
||||||
|
return self._create_response(request, errors, status_code=exc.status_code)
|
||||||
|
|
||||||
response = self._handle_duplicate_email_username(request, data)
|
response = self._handle_duplicate_email_username(request, data)
|
||||||
if response:
|
if response:
|
||||||
return response
|
return response
|
||||||
|
|||||||
274
openedx/core/djangoapps/user_authn/views/tests/test_filters.py
Normal file
274
openedx/core/djangoapps/user_authn/views/tests/test_filters.py
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
"""
|
||||||
|
Test that various filters are fired for the vies in the user_authn app.
|
||||||
|
"""
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.test import override_settings
|
||||||
|
from django.urls import reverse
|
||||||
|
from openedx_filters import PipelineStep
|
||||||
|
from openedx_filters.learning.filters import StudentLoginRequested, StudentRegistrationRequested
|
||||||
|
from rest_framework import status
|
||||||
|
|
||||||
|
from common.djangoapps.student.tests.factories import UserFactory, UserProfileFactory
|
||||||
|
from openedx.core.djangoapps.user_api.tests.test_views import UserAPITestCase
|
||||||
|
from openedx.core.djangolib.testing.utils import skip_unless_lms
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
class TestRegisterPipelineStep(PipelineStep):
|
||||||
|
"""
|
||||||
|
Utility function used when getting steps for pipeline.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def run_filter(self, form_data): # pylint: disable=arguments-differ
|
||||||
|
"""Pipeline steps that changes the user's username."""
|
||||||
|
username = f"{form_data.get('username')}-OpenEdx"
|
||||||
|
form_data["username"] = username
|
||||||
|
return {
|
||||||
|
"form_data": form_data,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestAnotherRegisterPipelineStep(PipelineStep):
|
||||||
|
"""
|
||||||
|
Utility function used when getting steps for pipeline.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def run_filter(self, form_data): # pylint: disable=arguments-differ
|
||||||
|
"""Pipeline steps that changes the user's username."""
|
||||||
|
username = f"{form_data.get('username')}-Test"
|
||||||
|
form_data["username"] = username
|
||||||
|
return {
|
||||||
|
"form_data": form_data,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestStopRegisterPipelineStep(PipelineStep):
|
||||||
|
"""
|
||||||
|
Utility function used when getting steps for pipeline.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def run_filter(self, form_data): # pylint: disable=arguments-differ
|
||||||
|
"""Pipeline steps that stops the user's registration process."""
|
||||||
|
raise StudentRegistrationRequested.PreventRegistration("You can't register on this site.", status_code=403)
|
||||||
|
|
||||||
|
|
||||||
|
class TestLoginPipelineStep(PipelineStep):
|
||||||
|
"""
|
||||||
|
Utility function used when getting steps for pipeline.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def run_filter(self, user): # pylint: disable=arguments-differ
|
||||||
|
"""Pipeline steps that adds a field to the user's profile."""
|
||||||
|
user.profile.set_meta({"logged_in": True})
|
||||||
|
user.profile.save()
|
||||||
|
return {
|
||||||
|
"user": user
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestAnotherLoginPipelineStep(PipelineStep):
|
||||||
|
"""
|
||||||
|
Utility function used when getting steps for pipeline.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def run_filter(self, user): # pylint: disable=arguments-differ
|
||||||
|
"""Pipeline steps that adds a field to the user's profile."""
|
||||||
|
new_meta = user.profile.get_meta()
|
||||||
|
new_meta.update({"another_logged_in": True})
|
||||||
|
user.profile.set_meta(new_meta)
|
||||||
|
user.profile.save()
|
||||||
|
return {
|
||||||
|
"user": user
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestStopLoginPipelineStep(PipelineStep):
|
||||||
|
"""
|
||||||
|
Utility function used when getting steps for pipeline.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def run_filter(self, user): # pylint: disable=arguments-differ
|
||||||
|
"""Pipeline steps that stops the user's login."""
|
||||||
|
raise StudentLoginRequested.PreventLogin("You can't login on this site.")
|
||||||
|
|
||||||
|
|
||||||
|
@skip_unless_lms
|
||||||
|
class RegistrationFiltersTest(UserAPITestCase):
|
||||||
|
"""
|
||||||
|
Tests for the Open edX Filters associated with the user registration process.
|
||||||
|
|
||||||
|
This class guarantees that the following filters are triggered during the user's registration:
|
||||||
|
|
||||||
|
- StudentRegistrationRequested
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self): # pylint: disable=arguments-differ
|
||||||
|
super().setUp()
|
||||||
|
self.url = reverse("user_api_registration")
|
||||||
|
self.user_info = {
|
||||||
|
"email": "user@example.com",
|
||||||
|
"name": "Test User",
|
||||||
|
"username": "test",
|
||||||
|
"password": "password",
|
||||||
|
"honor_code": "true",
|
||||||
|
}
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
OPEN_EDX_FILTERS_CONFIG={
|
||||||
|
"org.openedx.learning.student.registration.requested.v1": {
|
||||||
|
"pipeline": [
|
||||||
|
"openedx.core.djangoapps.user_authn.views.tests.test_filters.TestRegisterPipelineStep",
|
||||||
|
"openedx.core.djangoapps.user_authn.views.tests.test_filters.TestAnotherRegisterPipelineStep",
|
||||||
|
],
|
||||||
|
"fail_silently": False,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def test_register_filter_executed(self):
|
||||||
|
"""
|
||||||
|
Test whether the student register filter is triggered before the user's
|
||||||
|
registration process.
|
||||||
|
|
||||||
|
Expected result:
|
||||||
|
- StudentRegistrationRequested is triggered and executes TestRegisterPipelineStep.
|
||||||
|
- The user's username is updated.
|
||||||
|
"""
|
||||||
|
self.client.post(self.url, self.user_info)
|
||||||
|
|
||||||
|
user = User.objects.filter(username=f"{self.user_info.get('username')}-OpenEdx-Test")
|
||||||
|
self.assertTrue(user)
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
OPEN_EDX_FILTERS_CONFIG={
|
||||||
|
"org.openedx.learning.student.registration.requested.v1": {
|
||||||
|
"pipeline": [
|
||||||
|
"openedx.core.djangoapps.user_authn.views.tests.test_filters.TestRegisterPipelineStep",
|
||||||
|
"openedx.core.djangoapps.user_authn.views.tests.test_filters.TestStopRegisterPipelineStep",
|
||||||
|
],
|
||||||
|
"fail_silently": False,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def test_register_filter_prevent_registration(self):
|
||||||
|
"""
|
||||||
|
Test prevent the user's registration through a pipeline step.
|
||||||
|
|
||||||
|
Expected result:
|
||||||
|
- StudentRegistrationRequested is triggered and executes TestStopRegisterPipelineStep.
|
||||||
|
- The user's registration stops.
|
||||||
|
"""
|
||||||
|
response = self.client.post(self.url, self.user_info)
|
||||||
|
|
||||||
|
self.assertEqual(status.HTTP_403_FORBIDDEN, response.status_code)
|
||||||
|
|
||||||
|
@override_settings(OPEN_EDX_FILTERS_CONFIG={})
|
||||||
|
def test_register_without_filter_configuration(self):
|
||||||
|
"""
|
||||||
|
Test usual registration process, without filter's intervention.
|
||||||
|
|
||||||
|
Expected result:
|
||||||
|
- StudentRegistrationRequested does not have any effect on the registration process.
|
||||||
|
- The registration process ends successfully.
|
||||||
|
"""
|
||||||
|
self.client.post(self.url, self.user_info)
|
||||||
|
|
||||||
|
user = User.objects.filter(username=f"{self.user_info.get('username')}")
|
||||||
|
self.assertTrue(user)
|
||||||
|
|
||||||
|
|
||||||
|
@skip_unless_lms
|
||||||
|
class LoginFiltersTest(UserAPITestCase):
|
||||||
|
"""
|
||||||
|
Tests for the Open edX Filters associated with the user login process.
|
||||||
|
|
||||||
|
This class guarantees that the following filters are triggered during the user's login:
|
||||||
|
|
||||||
|
- StudentLoginRequested
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self): # pylint: disable=arguments-differ
|
||||||
|
super().setUp()
|
||||||
|
self.user = UserFactory.create(
|
||||||
|
username="test",
|
||||||
|
email="test@example.com",
|
||||||
|
password="password",
|
||||||
|
)
|
||||||
|
self.user_profile = UserProfileFactory.create(user=self.user, name="Test Example")
|
||||||
|
self.url = reverse('login_api')
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
OPEN_EDX_FILTERS_CONFIG={
|
||||||
|
"org.openedx.learning.student.login.requested.v1": {
|
||||||
|
"pipeline": [
|
||||||
|
"openedx.core.djangoapps.user_authn.views.tests.test_filters.TestLoginPipelineStep",
|
||||||
|
"openedx.core.djangoapps.user_authn.views.tests.test_filters.TestAnotherLoginPipelineStep",
|
||||||
|
],
|
||||||
|
"fail_silently": False,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def test_login_filter_executed(self):
|
||||||
|
"""
|
||||||
|
Test whether the student login filter is triggered before the user's
|
||||||
|
login process.
|
||||||
|
|
||||||
|
Expected result:
|
||||||
|
- StudentLoginRequested is triggered and executes TestLoginPipelineStep.
|
||||||
|
- The user's profile is updated.
|
||||||
|
"""
|
||||||
|
data = {
|
||||||
|
"email": "test@example.com",
|
||||||
|
"password": "password",
|
||||||
|
}
|
||||||
|
|
||||||
|
self.client.post(self.url, data)
|
||||||
|
|
||||||
|
user = User.objects.get(username=self.user.username)
|
||||||
|
self.assertDictEqual({"logged_in": True, "another_logged_in": True}, user.profile.get_meta())
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
OPEN_EDX_FILTERS_CONFIG={
|
||||||
|
"org.openedx.learning.student.login.requested.v1": {
|
||||||
|
"pipeline": [
|
||||||
|
"openedx.core.djangoapps.user_authn.views.tests.test_filters.TestLoginPipelineStep",
|
||||||
|
"openedx.core.djangoapps.user_authn.views.tests.test_filters.TestStopLoginPipelineStep",
|
||||||
|
],
|
||||||
|
"fail_silently": False,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def test_login_filter_prevent_login(self):
|
||||||
|
"""
|
||||||
|
Test prevent the user's login through a pipeline step.
|
||||||
|
|
||||||
|
Expected result:
|
||||||
|
- StudentLoginRequested is triggered and executes TestStopLoginPipelineStep.
|
||||||
|
- Test prevent the user's login through a pipeline step.
|
||||||
|
"""
|
||||||
|
data = {
|
||||||
|
"email": "test@example.com",
|
||||||
|
"password": "password",
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.post(self.url, data)
|
||||||
|
|
||||||
|
self.assertEqual(status.HTTP_400_BAD_REQUEST, response.status_code)
|
||||||
|
|
||||||
|
@override_settings(OPEN_EDX_FILTERS_CONFIG={})
|
||||||
|
def test_login_without_filter_configuration(self):
|
||||||
|
"""
|
||||||
|
Test usual login process, without filter's intervention.
|
||||||
|
|
||||||
|
Expected result:
|
||||||
|
- StudentLoginRequested does not have any effect on the login process.
|
||||||
|
- The login process ends successfully.
|
||||||
|
"""
|
||||||
|
data = {
|
||||||
|
"email": "test@example.com",
|
||||||
|
"password": "password",
|
||||||
|
}
|
||||||
|
|
||||||
|
response = self.client.post(self.url, data)
|
||||||
|
|
||||||
|
self.assertEqual(status.HTTP_200_OK, response.status_code)
|
||||||
@@ -120,6 +120,7 @@ nodeenv # Utility for managing Node.js environments;
|
|||||||
oauthlib # OAuth specification support for authenticating via LTI or other Open edX services
|
oauthlib # OAuth specification support for authenticating via LTI or other Open edX services
|
||||||
openedx-calc # Library supporting mathematical calculations for Open edX
|
openedx-calc # Library supporting mathematical calculations for Open edX
|
||||||
openedx-events # Open edX Events from Hooks Extension Framework (OEP-50)
|
openedx-events # Open edX Events from Hooks Extension Framework (OEP-50)
|
||||||
|
openedx-filters # Open edX Filters from Hooks Extension Framework (OEP-50)
|
||||||
ora2
|
ora2
|
||||||
piexif # Exif image metadata manipulation, used in the profile_images app
|
piexif # Exif image metadata manipulation, used in the profile_images app
|
||||||
Pillow # Image manipulation library; used for course assets, profile images, invoice PDFs, etc.
|
Pillow # Image manipulation library; used for course assets, profile images, invoice PDFs, etc.
|
||||||
|
|||||||
@@ -226,6 +226,7 @@ django==3.2.11
|
|||||||
# jsonfield
|
# jsonfield
|
||||||
# lti-consumer-xblock
|
# lti-consumer-xblock
|
||||||
# openedx-events
|
# openedx-events
|
||||||
|
# openedx-filters
|
||||||
# ora2
|
# ora2
|
||||||
# super-csv
|
# super-csv
|
||||||
# xss-utils
|
# xss-utils
|
||||||
@@ -701,6 +702,8 @@ openedx-calc==2.0.1
|
|||||||
# via -r requirements/edx/base.in
|
# via -r requirements/edx/base.in
|
||||||
openedx-events==0.7.1
|
openedx-events==0.7.1
|
||||||
# via -r requirements/edx/base.in
|
# via -r requirements/edx/base.in
|
||||||
|
openedx-filters==0.4.3
|
||||||
|
# via -r requirements/edx/base.in
|
||||||
ora2==3.7.8
|
ora2==3.7.8
|
||||||
# via -r requirements/edx/base.in
|
# via -r requirements/edx/base.in
|
||||||
packaging==21.3
|
packaging==21.3
|
||||||
|
|||||||
@@ -304,6 +304,7 @@ django==3.2.11
|
|||||||
# jsonfield
|
# jsonfield
|
||||||
# lti-consumer-xblock
|
# lti-consumer-xblock
|
||||||
# openedx-events
|
# openedx-events
|
||||||
|
# openedx-filters
|
||||||
# ora2
|
# ora2
|
||||||
# super-csv
|
# super-csv
|
||||||
# xss-utils
|
# xss-utils
|
||||||
@@ -935,6 +936,8 @@ openedx-calc==2.0.1
|
|||||||
# via -r requirements/edx/testing.txt
|
# via -r requirements/edx/testing.txt
|
||||||
openedx-events==0.7.1
|
openedx-events==0.7.1
|
||||||
# via -r requirements/edx/testing.txt
|
# via -r requirements/edx/testing.txt
|
||||||
|
openedx-filters==0.4.3
|
||||||
|
# via -r requirements/edx/testing.txt
|
||||||
ora2==3.7.8
|
ora2==3.7.8
|
||||||
# via -r requirements/edx/testing.txt
|
# via -r requirements/edx/testing.txt
|
||||||
packaging==21.3
|
packaging==21.3
|
||||||
|
|||||||
@@ -292,6 +292,7 @@ distlib==0.3.4
|
|||||||
# jsonfield
|
# jsonfield
|
||||||
# lti-consumer-xblock
|
# lti-consumer-xblock
|
||||||
# openedx-events
|
# openedx-events
|
||||||
|
# openedx-filters
|
||||||
# ora2
|
# ora2
|
||||||
# super-csv
|
# super-csv
|
||||||
# xss-utils
|
# xss-utils
|
||||||
@@ -885,6 +886,8 @@ openedx-calc==2.0.1
|
|||||||
# via -r requirements/edx/base.txt
|
# via -r requirements/edx/base.txt
|
||||||
openedx-events==0.7.1
|
openedx-events==0.7.1
|
||||||
# via -r requirements/edx/base.txt
|
# via -r requirements/edx/base.txt
|
||||||
|
openedx-filters==0.4.3
|
||||||
|
# via -r requirements/edx/base.txt
|
||||||
ora2==3.7.8
|
ora2==3.7.8
|
||||||
# via -r requirements/edx/base.txt
|
# via -r requirements/edx/base.txt
|
||||||
packaging==21.3
|
packaging==21.3
|
||||||
|
|||||||
Reference in New Issue
Block a user