Commit fd4164de by Ned Batchelder

Merge pull request #11737 from edx/release

Release ⇒ master
parents ad5cec10 00b270c3
...@@ -233,7 +233,7 @@ class TestCourseIndex(CourseTestCase): ...@@ -233,7 +233,7 @@ class TestCourseIndex(CourseTestCase):
# delete nofications that are dismissed # delete nofications that are dismissed
CourseRerunState.objects.get(id=rerun_state.id) CourseRerunState.objects.get(id=rerun_state.id)
self.assertTrue(has_course_author_access(user2, rerun_course_key)) self.assertFalse(has_course_author_access(user2, rerun_course_key))
def assert_correct_json_response(self, json_response): def assert_correct_json_response(self, json_response):
""" """
......
...@@ -54,10 +54,6 @@ class RoleCache(object): ...@@ -54,10 +54,6 @@ class RoleCache(object):
for access_role in self._roles for access_role in self._roles
) )
def add_role(self, role):
"""Adds a role to the cache."""
self._roles.add(role)
class AccessRole(object): class AccessRole(object):
""" """
...@@ -66,7 +62,7 @@ class AccessRole(object): ...@@ -66,7 +62,7 @@ class AccessRole(object):
__metaclass__ = ABCMeta __metaclass__ = ABCMeta
@abstractmethod @abstractmethod
def has_user(self, user, refresh=True): def has_user(self, user):
""" """
Return whether the supplied django user has access to this role. Return whether the supplied django user has access to this role.
""" """
...@@ -134,7 +130,7 @@ class RoleBase(AccessRole): ...@@ -134,7 +130,7 @@ class RoleBase(AccessRole):
self.course_key = course_key self.course_key = course_key
self._role_name = role_name self._role_name = role_name
def has_user(self, user, refresh=False): def has_user(self, user):
""" """
Return whether the supplied django user has access to this role. Return whether the supplied django user has access to this role.
""" """
...@@ -142,7 +138,7 @@ class RoleBase(AccessRole): ...@@ -142,7 +138,7 @@ class RoleBase(AccessRole):
return False return False
# pylint: disable=protected-access # pylint: disable=protected-access
if not hasattr(user, '_roles') or refresh: if not hasattr(user, '_roles'):
# Cache a list of tuples identifying the particular roles that a user has # Cache a list of tuples identifying the particular roles that a user has
# Stored as tuples, rather than django models, to make it cheaper to construct objects for comparison # Stored as tuples, rather than django models, to make it cheaper to construct objects for comparison
user._roles = RoleCache(user) user._roles = RoleCache(user)
...@@ -161,8 +157,7 @@ class RoleBase(AccessRole): ...@@ -161,8 +157,7 @@ class RoleBase(AccessRole):
entry = CourseAccessRole(user=user, role=self._role_name, course_id=self.course_key, org=self.org) entry = CourseAccessRole(user=user, role=self._role_name, course_id=self.course_key, org=self.org)
entry.save() entry.save()
if hasattr(user, '_roles'): if hasattr(user, '_roles'):
# pylint: disable=protected-access del user._roles
user._roles.add_role(entry)
def remove_users(self, *users): def remove_users(self, *users):
""" """
......
...@@ -40,6 +40,7 @@ from student.tests.factories import ( ...@@ -40,6 +40,7 @@ from student.tests.factories import (
from xmodule.x_module import XModuleMixin from xmodule.x_module import XModuleMixin
from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ( from xmodule.modulestore.tests.django_utils import (
ModuleStoreTestCase, ModuleStoreTestCase,
SharedModuleStoreTestCase, SharedModuleStoreTestCase,
...@@ -297,7 +298,7 @@ class TestCoachDashboard(CcxTestCase, LoginEnrollmentTestCase): ...@@ -297,7 +298,7 @@ class TestCoachDashboard(CcxTestCase, LoginEnrollmentTestCase):
# assert ccx creator has role=ccx_coach # assert ccx creator has role=ccx_coach
role = CourseCcxCoachRole(course_key) role = CourseCcxCoachRole(course_key)
self.assertTrue(role.has_user(self.coach, refresh=True)) self.assertTrue(role.has_user(self.coach))
@ddt.data("CCX demo 1", "CCX demo 2", "CCX demo 3") @ddt.data("CCX demo 1", "CCX demo 2", "CCX demo 3")
def test_create_multiple_ccx(self, ccx_name): def test_create_multiple_ccx(self, ccx_name):
...@@ -790,28 +791,25 @@ def patched_get_children(self, usage_key_filter=None): ...@@ -790,28 +791,25 @@ def patched_get_children(self, usage_key_filter=None):
@override_settings(FIELD_OVERRIDE_PROVIDERS=( @override_settings(FIELD_OVERRIDE_PROVIDERS=(
'ccx.overrides.CustomCoursesForEdxOverrideProvider',)) 'ccx.overrides.CustomCoursesForEdxOverrideProvider',))
@patch('xmodule.x_module.XModuleMixin.get_children', patched_get_children, spec=True) @patch('xmodule.x_module.XModuleMixin.get_children', patched_get_children, spec=True)
class TestCCXGrades(ModuleStoreTestCase, LoginEnrollmentTestCase): class TestCCXGrades(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
""" """
Tests for Custom Courses views. Tests for Custom Courses views.
""" """
MODULESTORE = TEST_DATA_SPLIT_MODULESTORE MODULESTORE = TEST_DATA_SPLIT_MODULESTORE
def setUp(self): @classmethod
""" def setUpClass(cls):
Set up tests super(TestCCXGrades, cls).setUpClass()
""" cls._course = course = CourseFactory.create(enable_ccx=True)
super(TestCCXGrades, self).setUp()
self._course = CourseFactory.create(enable_ccx=True)
# Create a course outline # Create a course outline
self.start = datetime.datetime( cls.mooc_start = start = datetime.datetime(
2010, 5, 12, 2, 42, tzinfo=pytz.UTC 2010, 5, 12, 2, 42, tzinfo=pytz.UTC
) )
chapter = ItemFactory.create( chapter = ItemFactory.create(
start=self.start, parent=self._course, category='sequential' start=start, parent=course, category='sequential'
) )
self.sections = [ cls.sections = sections = [
ItemFactory.create( ItemFactory.create(
parent=chapter, parent=chapter,
category="sequential", category="sequential",
...@@ -819,7 +817,7 @@ class TestCCXGrades(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -819,7 +817,7 @@ class TestCCXGrades(ModuleStoreTestCase, LoginEnrollmentTestCase):
for _ in xrange(4) for _ in xrange(4)
] ]
# making problems available at class level for possible future use in tests # making problems available at class level for possible future use in tests
self.problems = [ cls.problems = [
[ [
ItemFactory.create( ItemFactory.create(
parent=section, parent=section,
...@@ -827,9 +825,15 @@ class TestCCXGrades(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -827,9 +825,15 @@ class TestCCXGrades(ModuleStoreTestCase, LoginEnrollmentTestCase):
data=StringResponseXMLFactory().build_xml(answer='foo'), data=StringResponseXMLFactory().build_xml(answer='foo'),
metadata={'rerandomize': 'always'} metadata={'rerandomize': 'always'}
) for _ in xrange(4) ) for _ in xrange(4)
] for section in self.sections ] for section in sections
] ]
def setUp(self):
"""
Set up tests
"""
super(TestCCXGrades, self).setUp()
# Create instructor account # Create instructor account
self.coach = coach = AdminFactory.create() self.coach = coach = AdminFactory.create()
self.client.login(username=coach.username, password="test") self.client.login(username=coach.username, password="test")
...@@ -902,18 +906,13 @@ class TestCCXGrades(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -902,18 +906,13 @@ class TestCCXGrades(ModuleStoreTestCase, LoginEnrollmentTestCase):
rows = response.content.strip().split('\r') rows = response.content.strip().split('\r')
headers = rows[0] headers = rows[0]
records = dict() # picking first student records
for i in range(1, len(rows)): data = dict(zip(headers.strip().split(','), rows[1].strip().split(',')))
data = dict(zip(headers.strip().split(','), rows[i].strip().split(','))) self.assertNotIn('HW 04', data)
records[data['username']] = data self.assertEqual(data['HW 01'], '0.75')
self.assertEqual(data['HW 02'], '0.5')
student_data = records[self.student.username] # pylint: disable=no-member self.assertEqual(data['HW 03'], '0.25')
self.assertEqual(data['HW Avg'], '0.5')
self.assertNotIn('HW 04', student_data)
self.assertEqual(student_data['HW 01'], '0.75')
self.assertEqual(student_data['HW 02'], '0.5')
self.assertEqual(student_data['HW 03'], '0.25')
self.assertEqual(student_data['HW Avg'], '0.5')
@patch('courseware.views.render_to_response', intercept_renderer) @patch('courseware.views.render_to_response', intercept_renderer)
def test_student_progress(self): def test_student_progress(self):
......
...@@ -11,8 +11,6 @@ class Migration(migrations.Migration): ...@@ -11,8 +11,6 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.RemoveField( # Removed because we accidentally removed this column without first
model_name='courseoverview', # removing the code that refers to this. This can cause errors in production.
name='facebook_url',
),
] ]
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models, OperationalError, connection
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
class Migration(migrations.Migration): class Migration(migrations.Migration):
...@@ -10,10 +11,17 @@ class Migration(migrations.Migration): ...@@ -10,10 +11,17 @@ class Migration(migrations.Migration):
('course_overviews', '0008_remove_courseoverview_facebook_url'), ('course_overviews', '0008_remove_courseoverview_facebook_url'),
] ]
operations = [ # An original version of 0008 removed the facebook_url field
migrations.AddField( # We need to handle the case where our noop 0008 ran AND the case
# where the original 0008 ran. We do that by using Django's introspection
# API to query INFORMATION_SCHEMA. _meta is unavailable as the
# column has already been removed from the model.
fields = connection.introspection.get_table_description(connection.cursor(),'course_overviews_courseoverview')
operations = []
if not any(f.name == 'facebook_url' for f in fields):
operations += migrations.AddField(
model_name='courseoverview', model_name='courseoverview',
name='facebook_url', name='facebook_url',
field=models.TextField(null=True), field=models.TextField(null=True),
), ),
]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment