Merge pull request #28266 from eduNEXT/MJG/1st_batch_openedx_events

[BD-32] feat: 1st batch of Open edX Events
This commit is contained in:
Felipe Montoya
2021-09-02 10:08:44 -05:00
committed by GitHub
12 changed files with 504 additions and 11 deletions

View File

@@ -55,6 +55,13 @@ from simple_history.models import HistoricalRecords
from slumber.exceptions import HttpClientError, HttpServerError
from user_util import user_util
from openedx_events.learning.data import (
CourseData,
CourseEnrollmentData,
UserData,
UserPersonalData,
)
from openedx_events.learning.signals import COURSE_ENROLLMENT_CREATED
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.student.emails import send_proctoring_requirements_email
@@ -1569,9 +1576,16 @@ class CourseEnrollment(models.Model):
# All the server-side checks for whether a user is allowed to enroll.
try:
course = CourseOverview.get_from_id(course_key)
course_data = CourseData(
course_key=course.id,
display_name=course.display_name,
)
except CourseOverview.DoesNotExist:
# This is here to preserve legacy behavior which allowed enrollment in courses
# announced before the start of content creation.
course_data = CourseData(
course_key=course_key,
)
if check_access:
log.warning("User %s failed to enroll in non-existent course %s", user.username, str(course_key))
raise NonExistentCourseError # lint-amnesty, pylint: disable=raise-missing-from
@@ -1607,6 +1621,25 @@ class CourseEnrollment(models.Model):
enrollment.update_enrollment(is_active=True, mode=mode, enterprise_uuid=enterprise_uuid)
enrollment.send_signal(EnrollStatusChange.enroll)
# Announce user's enrollment
COURSE_ENROLLMENT_CREATED.send_event(
enrollment=CourseEnrollmentData(
user=UserData(
pii=UserPersonalData(
username=user.username,
email=user.email,
name=user.profile.name,
),
id=user.id,
is_active=user.is_active,
),
course=course_data,
mode=enrollment.mode,
is_active=enrollment.is_active,
creation_date=enrollment.created,
)
)
return enrollment
@classmethod

View File

@@ -10,6 +10,7 @@ import ddt
import pytest
from django.conf import settings
from django.urls import reverse
from openedx_events.tests.utils import OpenEdxEventsTestMixin
from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.course_modes.tests.factories import CourseModeFactory
@@ -30,11 +31,13 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
@ddt.ddt
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_SPECIAL_EXAMS': True})
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
class EnrollmentTest(UrlResetMixin, SharedModuleStoreTestCase):
class EnrollmentTest(UrlResetMixin, SharedModuleStoreTestCase, OpenEdxEventsTestMixin):
"""
Test student enrollment, especially with different course modes.
"""
ENABLED_OPENEDX_EVENTS = []
USERNAME = "Bob"
EMAIL = "bob@example.com"
PASSWORD = "edx"
@@ -42,7 +45,14 @@ class EnrollmentTest(UrlResetMixin, SharedModuleStoreTestCase):
@classmethod
def setUpClass(cls):
"""
Set up class method for the Test class.
This method starts manually events isolation. Explanation here:
openedx/core/djangoapps/user_authn/views/tests/test_events.py#L44
"""
super().setUpClass()
cls.start_events_isolation()
cls.course = CourseFactory.create()
cls.course_limited = CourseFactory.create()
cls.proctored_course = CourseFactory(

View File

@@ -10,10 +10,23 @@ from django.db.utils import IntegrityError
from django.test import TestCase
from django_countries.fields import Country
from common.djangoapps.student.models import CourseEnrollmentAllowed
from common.djangoapps.student.tests.factories import CourseEnrollmentAllowedFactory, UserFactory
from common.djangoapps.student.models import CourseEnrollmentAllowed, CourseEnrollment
from common.djangoapps.student.tests.factories import CourseEnrollmentAllowedFactory, UserFactory, UserProfileFactory
from common.djangoapps.student.tests.tests import UserSettingsEventTestMixin
from openedx_events.learning.data import (
CourseData,
CourseEnrollmentData,
UserData,
UserPersonalData,
)
from openedx_events.learning.signals import COURSE_ENROLLMENT_CREATED
from openedx_events.tests.utils import OpenEdxEventsTestMixin
from openedx.core.djangolib.testing.utils import skip_unless_lms
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
class TestUserProfileEvents(UserSettingsEventTestMixin, TestCase):
"""
@@ -179,3 +192,87 @@ class TestUserEvents(UserSettingsEventTestMixin, TestCase):
# CEAs shouldn't have been affected
assert CourseEnrollmentAllowed.objects.count() == 1
assert CourseEnrollmentAllowed.objects.filter(email='test@edx.org').count() == 1
@skip_unless_lms
class EnrollmentEventsTest(SharedModuleStoreTestCase, OpenEdxEventsTestMixin):
"""
Tests for the Open edX Events associated with the enrollment process through the enroll method.
This class guarantees that the following events are sent during the user's enrollment, with
the exact Data Attributes as the event definition stated:
- COURSE_ENROLLMENT_CREATED: sent after the user's enrollment.
"""
ENABLED_OPENEDX_EVENTS = ["org.openedx.learning.course.enrollment.created.v1"]
@classmethod
def setUpClass(cls):
"""
Set up class method for the Test class.
This method starts manually events isolation. Explanation here:
openedx/core/djangoapps/user_authn/views/tests/test_events.py#L44
"""
super().setUpClass()
cls.start_events_isolation()
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")
self.receiver_called = False
def _event_receiver_side_effect(self, **kwargs): # pylint: disable=unused-argument
"""
Used show that the Open edX Event was called by the Django signal handler.
"""
self.receiver_called = True
def test_enrollment_created_event_emitted(self):
"""
Test whether the student enrollment event is sent after the user's
enrollment process.
Expected result:
- COURSE_ENROLLMENT_CREATED is sent and received by the mocked receiver.
- The arguments that the receiver gets are the arguments sent by the event
except the metadata generated on the fly.
"""
event_receiver = mock.Mock(side_effect=self._event_receiver_side_effect)
COURSE_ENROLLMENT_CREATED.connect(event_receiver)
enrollment = CourseEnrollment.enroll(self.user, self.course.id)
self.assertTrue(self.receiver_called)
self.assertDictContainsSubset(
{
"signal": COURSE_ENROLLMENT_CREATED,
"sender": None,
"enrollment": CourseEnrollmentData(
user=UserData(
pii=UserPersonalData(
username=self.user.username,
email=self.user.email,
name=self.user.profile.name,
),
id=self.user.id,
is_active=self.user.is_active,
),
course=CourseData(
course_key=self.course.id,
display_name=self.course.display_name,
),
mode=enrollment.mode,
is_active=enrollment.is_active,
creation_date=enrollment.created,
),
},
event_receiver.call_args.kwargs
)

View File

@@ -29,6 +29,8 @@ from edx_django_utils.monitoring import set_custom_attribute
from ratelimit.decorators import ratelimit
from rest_framework.views import APIView
from openedx_events.learning.data import UserData, UserPersonalData
from openedx_events.learning.signals import SESSION_LOGIN_COMPLETED
from common.djangoapps.edxmako.shortcuts import render_to_response
from openedx.core.djangoapps.password_policy import compliance as password_policy_compliance
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
@@ -298,6 +300,19 @@ def _handle_successful_authentication_and_login(user, request):
django_login(request, user)
request.session.set_expiry(604800 * 4)
log.debug("Setting user session expiry to 4 weeks")
# Announce user's login
SESSION_LOGIN_COMPLETED.send_event(
user=UserData(
pii=UserPersonalData(
username=user.username,
email=user.email,
name=user.profile.name,
),
id=user.id,
is_active=user.is_active,
),
)
except Exception as exc:
AUDIT_LOG.critical("Login failed - Could not create session. Is memcached running?")
log.critical("Login failed - Could not create session. Is memcached running?")

View File

@@ -23,6 +23,8 @@ from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie
from django.views.decorators.debug import sensitive_post_parameters
from edx_django_utils.monitoring import set_custom_attribute
from edx_toggles.toggles import LegacyWaffleFlag, LegacyWaffleFlagNamespace
from openedx_events.learning.data import UserData, UserPersonalData
from openedx_events.learning.signals import STUDENT_REGISTRATION_COMPLETED
from pytz import UTC
from ratelimit.decorators import ratelimit
from requests import HTTPError
@@ -252,6 +254,18 @@ def create_account_with_params(request, params):
# Announce registration
REGISTER_USER.send(sender=None, user=user, registration=registration)
STUDENT_REGISTRATION_COMPLETED.send_event(
user=UserData(
pii=UserPersonalData(
username=user.username,
email=user.email,
name=user.profile.name,
),
id=user.id,
is_active=user.is_active,
),
)
create_comments_service_user(user)
try:

View File

@@ -0,0 +1,183 @@
"""
Test classes for the events sent in the registration process.
Classes:
RegistrationEventTest: Test event sent after registering a user through the
user API.
LoginSessionEventTest: Test event sent after creating the user's login session
user through the user API.
"""
from unittest.mock import Mock
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user
from django.urls import reverse
from openedx_events.learning.data import UserData, UserPersonalData
from openedx_events.learning.signals import SESSION_LOGIN_COMPLETED, STUDENT_REGISTRATION_COMPLETED
from openedx_events.tests.utils import OpenEdxEventsTestMixin
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
@skip_unless_lms
class RegistrationEventTest(UserAPITestCase, OpenEdxEventsTestMixin):
"""
Tests for the Open edX Events associated with the registration process through
the registration view.
This class guarantees that the following events are sent after registering
a user, with the exact Data Attributes as the event definition stated:
- STUDENT_REGISTRATION_COMPLETED: after the user's registration has been
completed.
"""
ENABLED_OPENEDX_EVENTS = ["org.openedx.learning.student.registration.completed.v1"]
@classmethod
def setUpClass(cls):
"""
Set up class method for the Test class.
So the Open edX Events Isolation starts, the setUpClass must be explicitly
called with the method that executes the isolation. We do this to avoid
MRO resolution conflicts with other sibling classes while ensuring the
isolation process begins.
"""
super().setUpClass()
cls.start_events_isolation()
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",
}
self.receiver_called = False
def _event_receiver_side_effect(self, **kwargs): # pylint: disable=unused-argument
"""
Used show that the Open edX Event was called by the Django signal handler.
"""
self.receiver_called = True
def test_send_registration_event(self):
"""
Test whether the student registration event is sent during the user's
registration process.
Expected result:
- STUDENT_REGISTRATION_COMPLETED is sent and received by the mocked receiver.
- The arguments that the receiver gets are the arguments sent by the event
except the metadata generated on the fly.
"""
event_receiver = Mock(side_effect=self._event_receiver_side_effect)
STUDENT_REGISTRATION_COMPLETED.connect(event_receiver)
self.client.post(self.url, self.user_info)
user = User.objects.get(username=self.user_info.get("username"))
self.assertTrue(self.receiver_called)
self.assertDictContainsSubset(
{
"signal": STUDENT_REGISTRATION_COMPLETED,
"sender": None,
"user": UserData(
pii=UserPersonalData(
username=user.username,
email=user.email,
name=user.profile.name,
),
id=user.id,
is_active=user.is_active,
),
},
event_receiver.call_args.kwargs
)
@skip_unless_lms
class LoginSessionEventTest(UserAPITestCase, OpenEdxEventsTestMixin):
"""
Tests for the Open edX Events associated with the login process through the
login_user view.
This class guarantees that the following events are sent after the user's
session creation, with the exact Data Attributes as the event definition
stated:
- SESSION_LOGIN_COMPLETED: after login has been completed.
"""
ENABLED_OPENEDX_EVENTS = ["org.openedx.learning.auth.session.login.completed.v1"]
@classmethod
def setUpClass(cls):
"""
Set up class method for the Test class.
This method starts manually events isolation. Explanation here:
openedx/core/djangoapps/user_authn/views/tests/test_events.py#L44
"""
super().setUpClass()
cls.start_events_isolation()
def setUp(self): # pylint: disable=arguments-differ
super().setUp()
self.url = reverse("user_api_login_session", kwargs={"api_version": "v1"})
self.user = UserFactory.create(
username="test",
email="test@example.com",
password="password",
)
self.user_profile = UserProfileFactory.create(user=self.user, name="Test Example")
self.receiver_called = True
def _event_receiver_side_effect(self, **kwargs): # pylint: disable=unused-argument
"""
Used show that the Open edX Event was called by the Django signal handler.
"""
self.receiver_called = True
def test_send_login_event(self):
"""
Test whether the student login event is sent after the user's
login process.
Expected result:
- SESSION_LOGIN_COMPLETED is sent and received by the mocked receiver.
- The arguments that the receiver gets are the arguments sent by the event
except the metadata generated on the fly.
"""
event_receiver = Mock(side_effect=self._event_receiver_side_effect)
SESSION_LOGIN_COMPLETED.connect(event_receiver)
data = {
"email": "test@example.com",
"password": "password",
}
self.client.post(self.url, data)
user = User.objects.get(username=self.user.username)
self.assertTrue(self.receiver_called)
self.assertDictContainsSubset(
{
"signal": SESSION_LOGIN_COMPLETED,
"sender": None,
"user": UserData(
pii=UserPersonalData(
username=user.username,
email=user.email,
name=user.profile.name,
),
id=user.id,
is_active=user.is_active,
),
},
event_receiver.call_args.kwargs
)

View File

@@ -21,6 +21,7 @@ from django.urls import NoReverseMatch, reverse
from edx_toggles.toggles.testutils import override_waffle_flag, override_waffle_switch
from freezegun import freeze_time
from common.djangoapps.student.tests.factories import RegistrationFactory, UserFactory, UserProfileFactory
from openedx_events.tests.utils import OpenEdxEventsTestMixin
from openedx.core.djangoapps.password_policy.compliance import (
NonCompliantPasswordException,
@@ -44,11 +45,13 @@ from common.djangoapps.util.password_policy_validators import DEFAULT_MAX_PASSWO
@ddt.ddt
class LoginTest(SiteMixin, CacheIsolationTestCase):
class LoginTest(SiteMixin, CacheIsolationTestCase, OpenEdxEventsTestMixin):
"""
Test login_user() view
"""
ENABLED_OPENEDX_EVENTS = []
ENABLED_CACHES = ['default']
LOGIN_FAILED_WARNING = 'Email or password is incorrect'
ACTIVATE_ACCOUNT_WARNING = 'In order to sign in, you need to activate your account'
@@ -56,6 +59,17 @@ class LoginTest(SiteMixin, CacheIsolationTestCase):
user_email = 'test@edx.org'
password = 'test_password'
@classmethod
def setUpClass(cls):
"""
Set up class method for the Test class.
This method starts manually events isolation. Explanation here:
openedx/core/djangoapps/user_authn/views/tests/test_events.py#L44
"""
super().setUpClass()
cls.start_events_isolation()
def setUp(self):
"""Setup a test user along with its registration and profile"""
super().setUp()
@@ -954,13 +968,26 @@ class LoginTest(SiteMixin, CacheIsolationTestCase):
@ddt.ddt
@skip_unless_lms
class LoginSessionViewTest(ApiTestCase):
class LoginSessionViewTest(ApiTestCase, OpenEdxEventsTestMixin):
"""Tests for the login end-points of the user API. """
ENABLED_OPENEDX_EVENTS = []
USERNAME = "bob"
EMAIL = "bob@example.com"
PASSWORD = "password"
@classmethod
def setUpClass(cls):
"""
Set up class method for the Test class.
This method starts manually events isolation. Explanation here:
openedx/core/djangoapps/user_authn/views/tests/test_events.py#L44
"""
super().setUpClass()
cls.start_events_isolation()
def setUp(self):
super().setUp()
self.url = reverse("user_api_login_session", kwargs={'api_version': 'v1'})

View File

@@ -17,6 +17,7 @@ from django.test.utils import override_settings
from django.urls import reverse
from pytz import UTC
from social_django.models import Partial, UserSocialAuth
from openedx_events.tests.utils import OpenEdxEventsTestMixin
from edx_toggles.toggles.testutils import override_waffle_flag
from openedx.core.djangoapps.site_configuration.helpers import get_value
@@ -69,12 +70,16 @@ from common.djangoapps.util.password_policy_validators import (
@ddt.ddt
@skip_unless_lms
class RegistrationViewValidationErrorTest(ThirdPartyAuthTestMixin, UserAPITestCase, RetirementTestCase):
class RegistrationViewValidationErrorTest(
ThirdPartyAuthTestMixin, UserAPITestCase, RetirementTestCase, OpenEdxEventsTestMixin
):
"""
Tests for catching duplicate email and username validation errors within
the registration end-points of the User API.
"""
ENABLED_OPENEDX_EVENTS = []
maxDiff = None
USERNAME = "bob"
@@ -88,6 +93,17 @@ class RegistrationViewValidationErrorTest(ThirdPartyAuthTestMixin, UserAPITestCa
COUNTRY = "us"
GOALS = "Learn all the things!"
@classmethod
def setUpClass(cls):
"""
Set up class method for the Test class.
This method starts manually events isolation. Explanation here:
openedx/core/djangoapps/user_authn/views/tests/test_events.py#L44
"""
super().setUpClass()
cls.start_events_isolation()
def setUp(self): # pylint: disable=arguments-differ
super().setUp()
self.url = reverse("user_api_registration")
@@ -423,9 +439,13 @@ class RegistrationViewValidationErrorTest(ThirdPartyAuthTestMixin, UserAPITestCa
@ddt.ddt
@skip_unless_lms
class RegistrationViewTestV1(ThirdPartyAuthTestMixin, UserAPITestCase):
class RegistrationViewTestV1(
ThirdPartyAuthTestMixin, UserAPITestCase, OpenEdxEventsTestMixin
):
"""Tests for the registration end-points of the User API. """
ENABLED_OPENEDX_EVENTS = []
maxDiff = None
USERNAME = "bob"
@@ -486,6 +506,17 @@ class RegistrationViewTestV1(ThirdPartyAuthTestMixin, UserAPITestCase):
]
link_template = "<a href='/honor' rel='noopener' target='_blank'>{link_label}</a>"
@classmethod
def setUpClass(cls):
"""
Set up class method for the Test class.
This method starts manually events isolation. Explanation here:
openedx/core/djangoapps/user_authn/views/tests/test_events.py#L44
"""
super().setUpClass()
cls.start_events_isolation()
def setUp(self): # pylint: disable=arguments-differ
super().setUp()
self.url = reverse("user_api_registration")
@@ -1815,6 +1846,17 @@ class RegistrationViewTestV2(RegistrationViewTestV1):
# pylint: disable=test-inherits-tests
@classmethod
def setUpClass(cls):
"""
Set up class method for the Test class.
This method starts manually events isolation. Explanation here:
openedx/core/djangoapps/user_authn/views/tests/test_events.py#L44
"""
super().setUpClass()
cls.start_events_isolation()
def setUp(self): # pylint: disable=arguments-differ
super(RegistrationViewTestV1, self).setUp() # lint-amnesty, pylint: disable=bad-super-call
self.url = reverse("user_api_registration_v2")
@@ -2043,16 +2085,31 @@ class RegistrationViewTestV2(RegistrationViewTestV1):
@httpretty.activate
@ddt.ddt
class ThirdPartyRegistrationTestMixin(ThirdPartyOAuthTestMixin, CacheIsolationTestCase):
class ThirdPartyRegistrationTestMixin(
ThirdPartyOAuthTestMixin, CacheIsolationTestCase, OpenEdxEventsTestMixin
):
"""
Tests for the User API registration endpoint with 3rd party authentication.
"""
CREATE_USER = False
ENABLED_OPENEDX_EVENTS = []
ENABLED_CACHES = ['default']
__test__ = False
@classmethod
def setUpClass(cls):
"""
Set up class method for the Test class.
This method starts manually events isolation. Explanation here:
openedx/core/djangoapps/user_authn/views/tests/test_events.py#L44
"""
super().setUpClass()
cls.start_events_isolation()
def setUp(self):
super().setUp()
self.url = reverse('user_api_registration')
@@ -2209,11 +2266,25 @@ class ThirdPartyRegistrationTestMixin(ThirdPartyOAuthTestMixin, CacheIsolationTe
@skipUnless(settings.FEATURES.get("ENABLE_THIRD_PARTY_AUTH"), "third party auth not enabled")
class TestFacebookRegistrationView(
ThirdPartyRegistrationTestMixin, ThirdPartyOAuthTestMixinFacebook, TransactionTestCase
ThirdPartyRegistrationTestMixin, ThirdPartyOAuthTestMixinFacebook, TransactionTestCase, OpenEdxEventsTestMixin
):
"""Tests the User API registration endpoint with Facebook authentication."""
ENABLED_OPENEDX_EVENTS = []
__test__ = True
@classmethod
def setUpClass(cls):
"""
Set up class method for the Test class.
This method starts manually events isolation. Explanation here:
openedx/core/djangoapps/user_authn/views/tests/test_events.py#L44
"""
super().setUpClass()
cls.start_events_isolation()
def test_social_auth_exception(self):
"""
According to the do_auth method in social_core.backends.facebook.py,
@@ -2227,21 +2298,48 @@ class TestFacebookRegistrationView(
@skipUnless(settings.FEATURES.get("ENABLE_THIRD_PARTY_AUTH"), "third party auth not enabled")
class TestGoogleRegistrationView(
ThirdPartyRegistrationTestMixin, ThirdPartyOAuthTestMixinGoogle, TransactionTestCase
ThirdPartyRegistrationTestMixin, ThirdPartyOAuthTestMixinGoogle, TransactionTestCase, OpenEdxEventsTestMixin
):
"""Tests the User API registration endpoint with Google authentication."""
ENABLED_OPENEDX_EVENTS = []
__test__ = True
@classmethod
def setUpClass(cls):
"""
Set up class method for the Test class.
This method starts manually events isolation. Explanation here:
openedx/core/djangoapps/user_authn/views/tests/test_events.py#L44
"""
super().setUpClass()
cls.start_events_isolation()
@ddt.ddt
class RegistrationValidationViewTests(test_utils.ApiTestCase):
class RegistrationValidationViewTests(test_utils.ApiTestCase, OpenEdxEventsTestMixin):
"""
Tests for validity of user data in registration forms.
"""
ENABLED_OPENEDX_EVENTS = []
endpoint_name = 'registration_validation'
path = reverse(endpoint_name)
@classmethod
def setUpClass(cls):
"""
Set up class method for the Test class.
This method starts manually events isolation. Explanation here:
openedx/core/djangoapps/user_authn/views/tests/test_events.py#L44
"""
super().setUpClass()
cls.start_events_isolation()
def setUp(self):
super().setUp()
cache.clear()

View File

@@ -122,6 +122,7 @@ nltk # Natural language processing; used by the c
nodeenv # Utility for managing Node.js environments; we use this for deployments and testing
oauthlib # OAuth specification support for authenticating via LTI or other Open edX services
openedx-calc # Library supporting mathematical calculations for Open edX
openedx-events # Open edX Events from Hooks Extension Framework (OEP-50)
ora2
piexif # Exif image metadata manipulation, used in the profile_images app
Pillow # Image manipulation library; used for course assets, profile images, invoice PDFs, etc.

View File

@@ -53,6 +53,7 @@ attrs==21.2.0
# -r requirements/edx/base.in
# aiohttp
# edx-ace
# openedx-events
babel==2.9.1
# via
# -r requirements/edx/base.in
@@ -220,6 +221,7 @@ django==2.2.24
# help-tokens
# jsonfield2
# lti-consumer-xblock
# openedx-events
# ora2
# rest-condition
# super-csv
@@ -469,6 +471,7 @@ edx-opaque-keys[django]==2.2.2
# edx-user-state-client
# edx-when
# lti-consumer-xblock
# openedx-events
# ora2
# xmodule
edx-organizations==6.10.0
@@ -702,6 +705,8 @@ oauthlib==3.0.1
# social-auth-core
openedx-calc==2.0.1
# via -r requirements/edx/base.in
openedx-events==0.5.1
# via -r requirements/edx/base.in
ora2==3.6.20
# via -r requirements/edx/base.in
packaging==21.0

View File

@@ -74,6 +74,7 @@ attrs==21.2.0
# aiohttp
# edx-ace
# jsonschema
# openedx-events
# pytest
babel==2.9.1
# via
@@ -298,6 +299,7 @@ django==2.2.24
# help-tokens
# jsonfield2
# lti-consumer-xblock
# openedx-events
# ora2
# rest-condition
# super-csv
@@ -574,6 +576,7 @@ edx-opaque-keys[django]==2.2.2
# edx-user-state-client
# edx-when
# lti-consumer-xblock
# openedx-events
# ora2
# xmodule
edx-organizations==6.10.0
@@ -929,6 +932,8 @@ oauthlib==3.0.1
# social-auth-core
openedx-calc==2.0.1
# via -r requirements/edx/testing.txt
openedx-events==0.5.1
# via -r requirements/edx/testing.txt
ora2==3.6.20
# via -r requirements/edx/testing.txt
packaging==21.0

View File

@@ -68,6 +68,7 @@ attrs==21.2.0
# -r requirements/edx/base.txt
# aiohttp
# edx-ace
# openedx-events
# pytest
babel==2.9.1
# via
@@ -283,6 +284,7 @@ distlib==0.3.2
# help-tokens
# jsonfield2
# lti-consumer-xblock
# openedx-events
# ora2
# rest-condition
# super-csv
@@ -556,6 +558,7 @@ edx-opaque-keys[django]==2.2.2
# edx-user-state-client
# edx-when
# lti-consumer-xblock
# openedx-events
# ora2
# xmodule
edx-organizations==6.10.0
@@ -877,6 +880,8 @@ oauthlib==3.0.1
# social-auth-core
openedx-calc==2.0.1
# via -r requirements/edx/base.txt
openedx-events==0.5.1
# via -r requirements/edx/base.txt
ora2==3.6.20
# via -r requirements/edx/base.txt
packaging==21.0