Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions common/djangoapps/student/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@

from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.request_cache import clear_cache, get_cache
from openedx.core.djangoapps.signals.signals import USER_ACCOUNT_ACTIVATED
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.xmodule_django.models import NoneToEmptyManager
from openedx.core.djangolib.model_mixins import DeletableByUserValue
Expand Down Expand Up @@ -728,6 +729,7 @@ def activate(self):
self.user.is_active = True
self._track_activation()
self.user.save()
USER_ACCOUNT_ACTIVATED.send_robust(self.__class__, user=self.user)
log.info(u'User %s (%s) account is successfully activated.', self.user.username, self.user.email)

def _track_activation(self):
Expand Down
13 changes: 13 additions & 0 deletions common/djangoapps/student/tests/test_activate_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,19 @@ def test_activation_with_keys(self, mock_segment_identify):
expected_segment_mailchimp_list
)

@patch('student.models.USER_ACCOUNT_ACTIVATED')
def test_activation_signal(self, mock_signal):
"""
Verify that USER_ACCOUNT_ACTIVATED is emitted upon account email activation.

Appsembler: This is a custom code to be pushed upstream.
"""
assert not self.user.is_active, 'Ensure that the user starts inactive'
assert not mock_signal.send_robust.call_count, 'Ensure no signal is fired before activation'
self.registration.activate() # Until you explicitly activate it
assert self.user.is_active, 'Sanity check for .activate()'
mock_signal.send_robust.assert_called_once_with(Registration, user=self.user), 'Ensure the signal is emitted'

@override_settings(LMS_SEGMENT_KEY="testkey")
@patch('student.models.analytics.identify')
def test_activation_without_mailchimp_key(self, mock_segment_identify):
Expand Down
1 change: 1 addition & 0 deletions common/lib/capa/capa/capa_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@

# These should be removed from HTML output, including all subelements
html_problem_semantics = [
"additional_answer",
"codeparam",
"responseparam",
"answer",
Expand Down
17 changes: 17 additions & 0 deletions common/lib/capa/capa/tests/test_capa_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,23 @@ def test_multiple_descriptions(self):
}
)

def test_additional_answer_is_skipped_from_resulting_html(self):
"""Tests that additional_answer element is not present in transformed HTML"""
xml = """
<problem>
<p>Be sure to check your spelling.</p>
<stringresponse answer="War" type="ci">
<label>___ requires sacrifices.</label>
<description>Anyone who looks the world as if it was a game of chess deserves to lose.</description>
<additional_answer answer="optional acceptable variant of the correct answer"/>
<textline size="40"/>
</stringresponse>
</problem>
"""
problem = new_loncapa_problem(xml)
self.assertEqual(len(problem.extracted_tree.xpath('//additional_answer')), 0)
self.assertNotIn('additional_answer', problem.get_html())

def test_non_accessible_inputtype(self):
"""
Verify that tag with question text is not removed when inputtype is not fully accessible.
Expand Down
38 changes: 37 additions & 1 deletion openedx/core/djangoapps/appsembler/api/tests/test_user_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@
@mock.patch(APPSEMBLER_API_VIEWS_MODULE + '.UserIndexViewSet.throttle_classes', [])
class UserIndexViewSetTest(TestCase):

# Fixtures to be used for filtering
JANE_DUE_USERNAME = 'jane.due'
JANE_DUE_EMAIL = '{username}@user.api.example.com'.format(username=JANE_DUE_USERNAME)
NON_USER_EMAIL = 'not.for.a.user@user.api.example.com'

def setUp(self):
"""
Set up test data for site isolation
Expand All @@ -51,7 +56,12 @@ def setUp(self):
self.my_site_org = OrganizationFactory(sites=[self.my_site])

# Set up users and enrollments for 'my site'
self.my_site_users = [UserFactory() for i in range(3)]
self.my_site_users = [
UserFactory.create(email=self.JANE_DUE_EMAIL, username=self.JANE_DUE_USERNAME),
UserFactory.create(),
UserFactory.create(),
]

for user in self.my_site_users:
UserOrganizationMappingFactory(user=user,
organization=self.my_site_org)
Expand Down Expand Up @@ -90,6 +100,32 @@ def test_get_all_users_for_site(self):
user_ids = [rec['id'] for rec in results]
assert set(user_ids) == set([obj.id for obj in expected_users])

@ddt.unpack
@ddt.data(
{'email': JANE_DUE_EMAIL.lower(), 'expected_count': 1, 'msg': 'Should find Jane (lower case) in the users'},
{'email': JANE_DUE_EMAIL.upper(), 'expected_count': 1, 'msg': 'Should find Jane (upper case) in the users'},
{'email': JANE_DUE_USERNAME, 'expected_count': 0, 'msg': 'Should not do partial matching'},
{'email': NON_USER_EMAIL, 'expected_count': 0, 'msg': 'Should not match any user.'},
)
def test_filter_by_email(self, email, expected_count, msg):
"""
Test the email filters matching.
"""
url = reverse('tahoe-api:v1:users-list')
request = APIRequestFactory().get(url, {'email_exact': email})
request.META['HTTP_HOST'] = self.my_site.domain
force_authenticate(request, user=self.caller)

view = resolve(url).func
response = view(request)
response.render()
results = response.data['results']

assert len(results) == expected_count, msg
if expected_count:
# Ignore the email case
assert results[0]['email'].lower() == email.lower(), msg

@skip("Need to implement user filter")
def test_get_all_enrolled_learners_for_site(self):

Expand Down
13 changes: 13 additions & 0 deletions openedx/core/djangoapps/appsembler/api/v1/filters.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

import django_filters
from django.contrib.auth import get_user_model
from opaque_keys.edx.keys import CourseKey
from openedx.core.djangoapps.content.course_overviews.models import (
CourseOverview,
Expand Down Expand Up @@ -68,3 +69,15 @@ def filter_course_id(self, queryset, name, value):
class Meta:
model = CourseEnrollment
fields = ['course_id', 'user_id', 'username', 'is_active', ]


class UserIndexFilter(django_filters.FilterSet):
'''Provides filtering for the User model objects in the UserIndexViewSet.

'''

email_exact = django_filters.CharFilter(name='email', lookup_expr='iexact')

class Meta:
model = get_user_model()
fields = ['email_exact']
4 changes: 3 additions & 1 deletion openedx/core/djangoapps/appsembler/api/v1/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@
from openedx.core.djangoapps.appsembler.api.v1.api import enroll_learners_in_course
from openedx.core.djangoapps.appsembler.api.v1.filters import (
CourseEnrollmentFilter,
CourseOverviewFilter
CourseOverviewFilter,
UserIndexFilter,
)
from openedx.core.djangoapps.appsembler.api.v1.pagination import (
TahoeLimitOffsetPagination
Expand Down Expand Up @@ -397,6 +398,7 @@ class UserIndexViewSet(TahoeAuthMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = UserIndexSerializer
throttle_classes = (TahoeAPIUserThrottle,)
filter_backends = (DjangoFilterBackend, )
filter_class = UserIndexFilter

def get_queryset(self):
site = django.contrib.sites.shortcuts.get_current_site(self.request)
Expand Down
4 changes: 3 additions & 1 deletion openedx/core/djangoapps/signals/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,7 @@
]
)

# Signal that indicates that a user has become verified
# Signal that indicates that a user has become verified for certificate purposes
LEARNER_NOW_VERIFIED = Signal(providing_args=['user'])

USER_ACCOUNT_ACTIVATED = Signal(providing_args=["user"]) # Signal indicating email verification
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ commands =
bash scripts/upgrade_pysqlite.sh
{env:TRAVIS_FIXES}
pytest \
common/djangoapps/student/tests/test_activate_account.py \
common/djangoapps/util/tests/test_milestones_helpers.py \
common/lib/xmodule/xmodule/modulestore/tests/test_split_mongo_mongo_connection.py

Expand Down