Merge remote-tracking branch 'origin/release' into bbeggs/2015-09-22-release-to-master
This commit is contained in:
@@ -6,7 +6,7 @@ from django.test.utils import override_settings
|
||||
from django.test.client import RequestFactory
|
||||
from django.conf import settings
|
||||
|
||||
from rest_framework.exceptions import AuthenticationFailed
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
|
||||
from cors_csrf.authentication import SessionAuthenticationCrossDomainCsrf
|
||||
|
||||
@@ -24,7 +24,7 @@ class CrossDomainAuthTest(TestCase):
|
||||
|
||||
def test_perform_csrf_referer_check(self):
|
||||
request = self._fake_request()
|
||||
with self.assertRaisesRegexp(AuthenticationFailed, 'CSRF'):
|
||||
with self.assertRaisesRegexp(PermissionDenied, 'CSRF'):
|
||||
self.auth.enforce_csrf(request)
|
||||
|
||||
@patch.dict(settings.FEATURES, {
|
||||
|
||||
@@ -11,7 +11,7 @@ from enrollment.errors import (
|
||||
CourseNotFoundError, CourseEnrollmentClosedError, CourseEnrollmentFullError,
|
||||
CourseEnrollmentExistsError, UserNotFoundError, InvalidEnrollmentAttribute
|
||||
)
|
||||
from enrollment.serializers import CourseEnrollmentSerializer, CourseField
|
||||
from enrollment.serializers import CourseEnrollmentSerializer, CourseSerializer
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from student.models import (
|
||||
CourseEnrollment, NonExistentCourseError, EnrollmentClosedError,
|
||||
@@ -35,9 +35,30 @@ def get_course_enrollments(user_id):
|
||||
|
||||
"""
|
||||
qset = CourseEnrollment.objects.filter(
|
||||
user__username=user_id, is_active=True
|
||||
user__username=user_id,
|
||||
is_active=True
|
||||
).order_by('created')
|
||||
return CourseEnrollmentSerializer(qset).data
|
||||
|
||||
enrollments = CourseEnrollmentSerializer(qset, many=True).data
|
||||
|
||||
# Find deleted courses and filter them out of the results
|
||||
deleted = []
|
||||
valid = []
|
||||
for enrollment in enrollments:
|
||||
if enrollment.get("course_details") is not None:
|
||||
valid.append(enrollment)
|
||||
else:
|
||||
deleted.append(enrollment)
|
||||
|
||||
if deleted:
|
||||
log.warning(
|
||||
(
|
||||
u"Course enrollments for user %s reference "
|
||||
u"courses that do not exist (this can occur if a course is deleted)."
|
||||
), user_id,
|
||||
)
|
||||
|
||||
return valid
|
||||
|
||||
|
||||
def get_course_enrollment(username, course_id):
|
||||
@@ -271,4 +292,4 @@ def get_course_enrollment_info(course_id, include_expired=False):
|
||||
log.warning(msg)
|
||||
raise CourseNotFoundError(msg)
|
||||
else:
|
||||
return CourseField().to_native(course, include_expired=include_expired)
|
||||
return CourseSerializer(course, include_expired=include_expired).data
|
||||
|
||||
@@ -30,32 +30,36 @@ class StringListField(serializers.CharField):
|
||||
return [int(item) for item in items]
|
||||
|
||||
|
||||
class CourseField(serializers.RelatedField):
|
||||
"""Read-Only representation of course enrollment information.
|
||||
|
||||
Aggregates course information from the CourseDescriptor as well as the Course Modes configured
|
||||
for enrolling in the course.
|
||||
|
||||
class CourseSerializer(serializers.Serializer): # pylint: disable=abstract-method
|
||||
"""
|
||||
Serialize a course descriptor and related information.
|
||||
"""
|
||||
|
||||
def to_native(self, course, **kwargs):
|
||||
course_modes = ModeSerializer(
|
||||
CourseMode.modes_for_course(
|
||||
course.id,
|
||||
include_expired=kwargs.get('include_expired', False),
|
||||
only_selectable=False
|
||||
)
|
||||
).data
|
||||
course_id = serializers.CharField(source="id")
|
||||
enrollment_start = serializers.DateTimeField(format=None)
|
||||
enrollment_end = serializers.DateTimeField(format=None)
|
||||
course_start = serializers.DateTimeField(source="start", format=None)
|
||||
course_end = serializers.DateTimeField(source="end", format=None)
|
||||
invite_only = serializers.BooleanField(source="invitation_only")
|
||||
course_modes = serializers.SerializerMethodField()
|
||||
|
||||
return {
|
||||
'course_id': unicode(course.id),
|
||||
'enrollment_start': course.enrollment_start,
|
||||
'enrollment_end': course.enrollment_end,
|
||||
'course_start': course.start,
|
||||
'course_end': course.end,
|
||||
'invite_only': course.invitation_only,
|
||||
'course_modes': course_modes,
|
||||
}
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.include_expired = kwargs.pop("include_expired", False)
|
||||
super(CourseSerializer, self).__init__(*args, **kwargs)
|
||||
|
||||
def get_course_modes(self, obj):
|
||||
"""
|
||||
Retrieve course modes associated with the course.
|
||||
"""
|
||||
course_modes = CourseMode.modes_for_course(
|
||||
obj.id,
|
||||
include_expired=self.include_expired,
|
||||
only_selectable=False
|
||||
)
|
||||
return [
|
||||
ModeSerializer(mode).data
|
||||
for mode in course_modes
|
||||
]
|
||||
|
||||
|
||||
class CourseEnrollmentSerializer(serializers.ModelSerializer):
|
||||
@@ -65,34 +69,9 @@ class CourseEnrollmentSerializer(serializers.ModelSerializer):
|
||||
the Course Descriptor and course modes, to give a complete representation of course enrollment.
|
||||
|
||||
"""
|
||||
course_details = serializers.SerializerMethodField('get_course_details')
|
||||
course_details = CourseSerializer(source="course_overview")
|
||||
user = serializers.SerializerMethodField('get_username')
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
serialized_data = super(CourseEnrollmentSerializer, self).data
|
||||
|
||||
# filter the results with empty courses 'course_details'
|
||||
if isinstance(serialized_data, dict):
|
||||
if serialized_data.get('course_details') is None:
|
||||
return None
|
||||
|
||||
return serialized_data
|
||||
|
||||
return [enrollment for enrollment in serialized_data if enrollment.get('course_details')]
|
||||
|
||||
def get_course_details(self, model):
|
||||
if model.course is None:
|
||||
msg = u"Course '{0}' does not exist (maybe deleted), in which User (user_id: '{1}') is enrolled.".format(
|
||||
model.course_id,
|
||||
model.user.id
|
||||
)
|
||||
log.warning(msg)
|
||||
return None
|
||||
|
||||
field = CourseField()
|
||||
return field.to_native(model.course)
|
||||
|
||||
def get_username(self, model):
|
||||
"""Retrieves the username from the associated model."""
|
||||
return model.username
|
||||
|
||||
@@ -1038,7 +1038,7 @@ class EnrollmentCrossDomainTest(ModuleStoreTestCase):
|
||||
@cross_domain_config
|
||||
def test_cross_domain_missing_csrf(self, *args): # pylint: disable=unused-argument
|
||||
resp = self._cross_domain_post('invalid_csrf_token')
|
||||
self.assertEqual(resp.status_code, 401)
|
||||
self.assertEqual(resp.status_code, 403)
|
||||
|
||||
def _get_csrf_cookie(self):
|
||||
"""Retrieve the cross-domain CSRF cookie. """
|
||||
|
||||
@@ -5,10 +5,18 @@ This module requires that :class:`request_cache.middleware.RequestCache`
|
||||
is installed in order to clear the cache after each request.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from urlparse import urlparse
|
||||
|
||||
from django.conf import settings
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
from request_cache import middleware
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_cache(name):
|
||||
"""
|
||||
Return the request cache named ``name``.
|
||||
@@ -26,3 +34,38 @@ def get_request():
|
||||
Return the current request.
|
||||
"""
|
||||
return middleware.RequestCache.get_current_request()
|
||||
|
||||
|
||||
def get_request_or_stub():
|
||||
"""
|
||||
Return the current request or a stub request.
|
||||
|
||||
If called outside the context of a request, construct a fake
|
||||
request that can be used to build an absolute URI.
|
||||
|
||||
This is useful in cases where we need to pass in a request object
|
||||
but don't have an active request (for example, in test cases).
|
||||
"""
|
||||
request = get_request()
|
||||
|
||||
if request is None:
|
||||
log.warning(
|
||||
"Could not retrieve the current request. "
|
||||
"A stub request will be created instead using settings.SITE_NAME. "
|
||||
"This should be used *only* in test cases, never in production!"
|
||||
)
|
||||
|
||||
# The settings SITE_NAME may contain a port number, so we need to
|
||||
# parse the full URL.
|
||||
full_url = "http://{site_name}".format(site_name=settings.SITE_NAME)
|
||||
parsed_url = urlparse(full_url)
|
||||
|
||||
# Construct the fake request. This can be used to construct absolute
|
||||
# URIs to other paths.
|
||||
return RequestFactory(
|
||||
SERVER_NAME=parsed_url.hostname,
|
||||
SERVER_PORT=parsed_url.port or 80,
|
||||
).get("/")
|
||||
|
||||
else:
|
||||
return request
|
||||
|
||||
20
common/djangoapps/request_cache/tests.py
Normal file
20
common/djangoapps/request_cache/tests.py
Normal file
@@ -0,0 +1,20 @@
|
||||
"""
|
||||
Tests for the request cache.
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.test import TestCase
|
||||
|
||||
from request_cache import get_request_or_stub
|
||||
|
||||
|
||||
class TestRequestCache(TestCase):
|
||||
"""
|
||||
Tests for the request cache.
|
||||
"""
|
||||
|
||||
def test_get_request_or_stub(self):
|
||||
# Outside the context of the request, we should still get a request
|
||||
# that allows us to build an absolute URI.
|
||||
stub = get_request_or_stub()
|
||||
expected_url = "http://{site_name}/foobar".format(site_name=settings.SITE_NAME)
|
||||
self.assertEqual(stub.build_absolute_uri("foobar"), expected_url)
|
||||
@@ -406,7 +406,7 @@ class BrowseTopicsTest(TeamsTabBase):
|
||||
)
|
||||
create_team_page.submit_form()
|
||||
team_page = TeamPage(self.browser, self.course_id)
|
||||
self.assertTrue(team_page.is_browser_on_page)
|
||||
self.assertTrue(team_page.is_browser_on_page())
|
||||
team_page.click_all_topics()
|
||||
self.assertTrue(self.topics_page.is_browser_on_page())
|
||||
self.topics_page.wait_for_ajax()
|
||||
|
||||
Reference in New Issue
Block a user